summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/config.json1
-rw-r--r--gui/src/main/daemon-rpc.ts49
-rw-r--r--gui/src/main/index.ts48
-rw-r--r--gui/src/main/notification-controller.ts10
-rw-r--r--gui/src/renderer/app.tsx28
-rw-r--r--gui/src/renderer/components/AdvancedSettings.tsx18
-rw-r--r--gui/src/renderer/components/WireguardKeys.tsx168
-rw-r--r--gui/src/renderer/components/WireguardKeysStyles.tsx48
-rw-r--r--gui/src/renderer/containers/AdvancedSettingsPage.tsx7
-rw-r--r--gui/src/renderer/containers/WireguardKeysPage.tsx27
-rw-r--r--gui/src/renderer/redux/settings/actions.ts73
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts117
-rw-r--r--gui/src/renderer/routes.tsx6
-rw-r--r--gui/src/shared/ipc-event-channel.ts33
14 files changed, 619 insertions, 14 deletions
diff --git a/gui/src/config.json b/gui/src/config.json
index 54eca6000b..9e11eea254 100644
--- a/gui/src/config.json
+++ b/gui/src/config.json
@@ -2,6 +2,7 @@
"links": {
"createAccount": "https://mullvad.net/account/create/",
"purchase": "https://mullvad.net/account/login/",
+ "manageKeys": "https://mullvad.net/account/login/",
"faq": "https://mullvad.net/faq/",
"download": "https://mullvad.net/download/",
"supportEmail": "support@mullvad.net"
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 91f9e8771c..e317ffb794 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -7,6 +7,7 @@ import {
ILocation,
IRelayList,
ISettings,
+ KeygenEvent,
RelaySettingsUpdate,
TunnelState,
} from '../shared/daemon-rpc-types';
@@ -330,6 +331,13 @@ const settingsSchema = partialObject({
tunnel_options: tunnelOptionsSchema,
});
+const keygenEventSchema = oneOf(
+ enumeration('too_many_keys', 'generation_failure'),
+ object({
+ new_key: string,
+ }),
+);
+
const daemonEventSchema = oneOf(
object({
tunnel_state: tunnelStateSchema,
@@ -341,12 +349,7 @@ const daemonEventSchema = oneOf(
relay_list: relayListSchema,
}),
object({
- wireguard_key: oneOf(
- enumeration('too_many_keys', 'generation_failure'),
- object({
- new_key: string,
- }),
- ),
+ wireguard_key: keygenEventSchema,
}),
);
@@ -524,6 +527,40 @@ export class DaemonRpc {
}
}
+ public async generateWireguardKey(): Promise<KeygenEvent> {
+ const response = await this.transport.send('generate_wireguard_key');
+ try {
+ const validatedResponse: any = validate(keygenEventSchema, response);
+ switch (validatedResponse) {
+ case 'too_many_keys':
+ case 'generation_failure':
+ return validatedResponse;
+ default:
+ return camelCaseObjectKeys(validatedResponse as object) as KeygenEvent;
+ }
+ } catch (error) {
+ throw new ResponseParseError(`Invalid response from generate_wireguard_key ${error}`);
+ }
+ }
+
+ public async getWireguardKey(): Promise<string | undefined> {
+ const response = await this.transport.send('get_wireguard_key');
+ try {
+ return validate(maybe(string), response) || undefined;
+ } catch (error) {
+ throw new ResponseParseError('Invalid response from get_wireguard_key');
+ }
+ }
+
+ public async verifyWireguardKey(): Promise<boolean> {
+ const response = await this.transport.send('verify_wireguard_key');
+ try {
+ return validate(boolean, response);
+ } catch (error) {
+ throw new ResponseParseError('Invalid response from verify_wireguard_key');
+ }
+ }
+
public async getVersionInfo(): Promise<IAppVersionInfo> {
const response = await this.transport.send('get_version_info', [], NETWORK_CALL_TIMEOUT);
try {
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index bdc1c4f5ec..a0dcbeb50b 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -13,6 +13,7 @@ import {
IRelayList,
IRelayListHostname,
ISettings,
+ KeygenEvent,
RelaySettings,
RelaySettingsUpdate,
TunnelState,
@@ -126,6 +127,8 @@ class ApplicationMain {
// The UI locale which is set once from onReady handler
private locale = 'en';
+ private wireguardPublicKey?: string;
+
public run() {
// Since electron's GPU blacklists are broken, GPU acceleration won't work on older distros
if (process.platform === 'linux') {
@@ -487,9 +490,7 @@ class ApplicationMain {
} else if ('relayList' in daemonEvent) {
this.setRelays(daemonEvent.relayList, this.settings.relaySettings);
} else if ('wireguardKey' in daemonEvent) {
- /// TODO: handle wireguard key events properly.
- log.info(`Received new key event`);
- log.info(daemonEvent);
+ this.handleWireguardKeygenEvent(daemonEvent.wireguardKey);
}
},
(error: Error) => {
@@ -512,6 +513,28 @@ class ApplicationMain {
}
}
+ private setWireguardKey(wireguardKey?: string) {
+ this.wireguardPublicKey = wireguardKey;
+ if (this.windowController) {
+ IpcMainEventChannel.wireguardKeys.notify(this.windowController.webContents, wireguardKey);
+ }
+ }
+
+ private handleWireguardKeygenEvent(event: KeygenEvent) {
+ switch (event) {
+ case 'too_many_keys':
+ case 'generation_failure':
+ this.notificationController.notifyKeyGenerationFailed();
+ this.wireguardPublicKey = undefined;
+ break;
+ default:
+ this.wireguardPublicKey = event.newKey;
+ }
+ if (this.windowController) {
+ IpcMainEventChannel.wireguardKeys.notifyKeygenEvent(this.windowController.webContents, event);
+ }
+ }
+
private setTunnelState(newState: TunnelState) {
this.tunnelState = newState;
this.updateTrayIcon(newState, this.settings.blockWhenDisconnected);
@@ -534,6 +557,7 @@ class ApplicationMain {
if (oldSettings.accountToken !== newSettings.accountToken) {
this.updateAccountHistory();
+ this.fetchWireguardKey();
}
if (this.windowController) {
@@ -831,6 +855,7 @@ class ApplicationMain {
currentVersion: this.currentVersion,
upgradeVersion: this.upgradeVersion,
guiSettings: this.guiSettings.state,
+ wireguardPublicKey: this.wireguardPublicKey,
}));
IpcMainEventChannel.settings.handleAllowLan((allowLan: boolean) =>
@@ -888,6 +913,15 @@ class ApplicationMain {
this.updateAccountHistory();
});
+ IpcMainEventChannel.wireguardKeys.handleGenerateKey(async () => {
+ try {
+ return await this.daemonRpc.generateWireguardKey();
+ } catch {
+ return 'generation_failure';
+ }
+ });
+ IpcMainEventChannel.wireguardKeys.handleVerifyKey(() => this.daemonRpc.verifyWireguardKey());
+
ipcMain.on('show-window', () => {
const windowController = this.windowController;
if (windowController) {
@@ -970,6 +1004,14 @@ class ApplicationMain {
}
}
+ private async fetchWireguardKey(): Promise<void> {
+ try {
+ this.setWireguardKey(await this.daemonRpc.getWireguardKey());
+ } catch (error) {
+ log.error(`Failed to fetch wireguard key: ${error.message}`);
+ }
+ }
+
private updateDaemonsAutoConnect() {
const daemonAutoConnect = this.guiSettings.autoConnect && getOpenAtLogin();
if (daemonAutoConnect !== this.settings.autoConnect) {
diff --git a/gui/src/main/notification-controller.ts b/gui/src/main/notification-controller.ts
index 771e788722..4b0f59fbf6 100644
--- a/gui/src/main/notification-controller.ts
+++ b/gui/src/main/notification-controller.ts
@@ -109,6 +109,16 @@ export default class NotificationController {
});
}
+ public notifyKeyGenerationFailed() {
+ const notification = new Notification({
+ title: this.notificationTitle,
+ body: messages.pgettext('notifications', 'Wireguard key generation failed'),
+ silent: true,
+ icon: this.notificationIcon,
+ });
+ this.scheduleNotification(notification);
+ }
+
public cancelPendingNotifications() {
for (const notification of this.pendingNotifications) {
notification.close();
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 3d5a6eb0e5..6d5d9ae5ab 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -36,6 +36,7 @@ import {
ILocation,
IRelayList,
ISettings,
+ KeygenEvent,
RelaySettings,
RelaySettingsUpdate,
TunnelState,
@@ -150,6 +151,14 @@ export default class AppRenderer {
this.storeAutoStart(autoStart);
});
+ IpcRendererEventChannel.wireguardKeys.listen((publicKey?: string) => {
+ this.setWireguardPublicKey(publicKey);
+ });
+
+ IpcRendererEventChannel.wireguardKeys.listenKeygenEvents((event: KeygenEvent) => {
+ this.reduxActions.settings.setWireguardKeygenEvent(event);
+ });
+
// Request the initial state from the main process
const initialState = IpcRendererEventChannel.state.get();
@@ -172,6 +181,7 @@ export default class AppRenderer {
this.setUpgradeVersion(initialState.upgradeVersion);
this.setGuiSettings(initialState.guiSettings);
this.storeAutoStart(initialState.autoStart);
+ this.setWireguardPublicKey(initialState.wireguardPublicKey);
if (initialState.isConnected) {
this.onDaemonConnected();
@@ -346,6 +356,20 @@ export default class AppRenderer {
IpcRendererEventChannel.guiSettings.setMonochromaticIcon(monochromaticIcon);
}
+ public async verifyWireguardKey(publicKey: string) {
+ const actions = this.reduxActions;
+ actions.settings.verifyWireguardKey(publicKey);
+ const valid = await IpcRendererEventChannel.wireguardKeys.verifyKey();
+ actions.settings.completeWireguardKeyVerification(valid);
+ }
+
+ public async generateWireguardKey() {
+ const actions = this.reduxActions;
+ actions.settings.generateWireguardKey();
+ const keygenEvent = await IpcRendererEventChannel.wireguardKeys.generateKey();
+ actions.settings.setWireguardKeygenEvent(keygenEvent);
+ }
+
private setRelaySettings(relaySettings: RelaySettings) {
const actions = this.reduxActions;
@@ -615,4 +639,8 @@ export default class AppRenderer {
private storeAutoStart(autoStart: boolean) {
this.reduxActions.settings.updateAutoStart(autoStart);
}
+
+ private setWireguardPublicKey(publicKey?: string) {
+ this.reduxActions.settings.setWireguardKey(publicKey);
+ }
}
diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx
index f5394a2520..1805e07bae 100644
--- a/gui/src/renderer/components/AdvancedSettings.tsx
+++ b/gui/src/renderer/components/AdvancedSettings.tsx
@@ -36,11 +36,13 @@ interface IProps {
mssfix?: number;
port?: number;
bridgeState: BridgeState;
+ enableWireguardKeysPage: boolean;
setBridgeState: (value: BridgeState) => void;
setEnableIpv6: (value: boolean) => void;
setBlockWhenDisconnected: (value: boolean) => void;
setOpenVpnMssfix: (value: number | undefined) => void;
setRelayProtocolAndPort: (protocol?: RelayProtocol, port?: number) => void;
+ onViewWireguardKeys: () => void;
onClose: () => void;
}
@@ -255,6 +257,7 @@ export default class AdvancedSettings extends Component<IProps, IState> {
},
)}
</Cell.Footer>
+ {this.wireguardKeysButton()}
</NavigationScrollbars>
</View>
</NavigationContainer>
@@ -264,6 +267,21 @@ export default class AdvancedSettings extends Component<IProps, IState> {
);
}
+ private wireguardKeysButton() {
+ if (this.props.enableWireguardKeysPage) {
+ return (
+ <View>
+ <Cell.CellButton onPress={this.props.onViewWireguardKeys}>
+ <Cell.Label>{messages.pgettext('advanced-settings-view', 'WireGuard keys')}</Cell.Label>
+ <Cell.Icon height={12} width={7} source="icon-chevron" />
+ </Cell.CellButton>
+ </View>
+ );
+ } else {
+ return null;
+ }
+ }
+
private onSelectProtocol = (protocol?: RelayProtocol) => {
this.props.setRelayProtocolAndPort(protocol);
};
diff --git a/gui/src/renderer/components/WireguardKeys.tsx b/gui/src/renderer/components/WireguardKeys.tsx
new file mode 100644
index 0000000000..dadaca4b51
--- /dev/null
+++ b/gui/src/renderer/components/WireguardKeys.tsx
@@ -0,0 +1,168 @@
+import * as React from 'react';
+import { Component, Text, View } from 'reactxp';
+import { messages } from '../../shared/gettext';
+import { WgKeyState } from '../redux/settings/reducers';
+import * as AppButton from './AppButton';
+import ImageView from './ImageView';
+import { Container, Layout } from './Layout';
+import { BackBarItem, NavigationBar, NavigationContainer } from './NavigationBar';
+import SettingsHeader, { HeaderTitle } from './SettingsHeader';
+import styles from './WireguardKeysStyles';
+
+export interface IProps {
+ keyState: WgKeyState;
+ isOffline: boolean;
+
+ onGenerateKey: () => void;
+ onVerifyKey: (publicKey: string) => void;
+ onVisitWebsiteKey: () => void;
+ onClose: () => void;
+}
+
+export default class WireguardKeys extends Component<IProps> {
+ public render() {
+ return (
+ <Layout>
+ <Container>
+ <View>
+ <NavigationContainer>
+ <NavigationBar>
+ <BackBarItem action={this.props.onClose}>
+ {// TRANSLATORS: Back button in navigation bar
+ messages.pgettext('wireguard-keys-nav', 'Advanced')}
+ </BackBarItem>
+ </NavigationBar>
+ </NavigationContainer>
+
+ <View style={styles.wgkeys__container}>
+ <SettingsHeader>
+ <HeaderTitle>
+ {messages.pgettext('wireguard-keys-nav', 'WireGuard keys')}
+ </HeaderTitle>
+ </SettingsHeader>
+
+ <View style={styles.wgkeys__row}>
+ <Text style={styles.wgkeys__row_label}>
+ {messages.pgettext('wireguard-keys', 'Public key')}
+ </Text>
+ <View style={styles.wgkeys__row_value}>{this.getKeyRow()}</View>
+ <View style={styles.wgkeys__validity_row}>{this.keyValidityLabel()}</View>
+ </View>
+
+ <View style={styles.wgkeys__row}>{this.getActionButton()}</View>
+ <View style={styles.wgkeys__row}>
+ <AppButton.GreenButton
+ disabled={this.props.isOffline}
+ onPress={this.props.onVisitWebsiteKey}>
+ <AppButton.Label>
+ {messages.pgettext('wireguard-key-view', 'Manage keys in website')}
+ </AppButton.Label>
+ <AppButton.Icon source="icon-extLink" height={16} width={16} />
+ </AppButton.GreenButton>
+ </View>
+ </View>
+ </View>
+ </Container>
+ </Layout>
+ );
+ }
+
+ /// Action button can either generate or verify a key
+ private getActionButton() {
+ switch (this.props.keyState.type) {
+ case 'key-set':
+ const publicKey = this.props.keyState.publicKey;
+ // if the key is known to be invalid, allow the user to generate a new one
+ if (this.props.keyState.valid === false) {
+ break;
+ }
+
+ const verificationCallback = () => this.props.onVerifyKey(publicKey);
+
+ return (
+ <AppButton.GreenButton disabled={this.props.isOffline} onPress={verificationCallback}>
+ <AppButton.Label>
+ {messages.pgettext('wireguard-key-view', 'Verify key')}
+ </AppButton.Label>
+ </AppButton.GreenButton>
+ );
+
+ case 'being-verified':
+ return this.busyButton(messages.pgettext('wireguard-key-view', 'Verifying key'));
+ case 'being-generated':
+ return this.busyButton(messages.pgettext('wireguard-key-view', 'Generating key'));
+ }
+ return (
+ <AppButton.GreenButton disabled={this.props.isOffline} onPress={this.props.onGenerateKey}>
+ <AppButton.Label>{messages.pgettext('wireguard-key-view', 'Generate key')}</AppButton.Label>
+ </AppButton.GreenButton>
+ );
+ }
+
+ private busyButton(message: string) {
+ return (
+ <AppButton.GreenButton disabled={true}>
+ <AppButton.Label>{message}</AppButton.Label>
+ <AppButton.Icon source="icon-spinner" height={16} width={16} />
+ </AppButton.GreenButton>
+ );
+ }
+
+ private getKeyRow() {
+ switch (this.props.keyState.type) {
+ case 'being-verified':
+ case 'key-set':
+ // mimicking the truncating of the key from website
+ return (
+ <View title={this.props.keyState.publicKey}>
+ <Text style={styles.wgkeys__row_value}>
+ {this.props.keyState.publicKey.substring(0, 20) + '...'}
+ </Text>
+ </View>
+ );
+ case 'being-generated':
+ return <ImageView source="icon-spinner" height={25} width={25} />;
+ case 'too-many-keys':
+ return (
+ <Text style={styles.wgkeys__invalid_key}>
+ {messages.pgettext('wireguard-key-view', 'Account has too many keys already')}
+ </Text>
+ );
+ case 'generation-failure':
+ return (
+ <Text style={styles.wgkeys__invalid_key}>
+ {messages.pgettext('wireguard-key-view', 'Failed to generate key')}
+ </Text>
+ );
+ default:
+ return (
+ <Text style={styles.wgkeys__row_value}>
+ {messages.pgettext('wireguard-key-view', 'No key set')}
+ </Text>
+ );
+ }
+ }
+
+ private keyValidityLabel() {
+ switch (this.props.keyState.type) {
+ case 'being-verified':
+ return <ImageView source="icon-spinner" height={25} width={25} />;
+ case 'key-set':
+ if (this.props.keyState.valid === true) {
+ return (
+ <Text style={styles.wgkeys__valid_key}>
+ {messages.pgettext('account-view', 'Key is valid')}
+ </Text>
+ );
+ } else if (this.props.keyState.valid === false) {
+ return (
+ <Text style={styles.wgkeys__invalid_key}>
+ {messages.pgettext('wireguard-key-view', 'Key is invalid')}
+ </Text>
+ );
+ }
+ default:
+ return '';
+ }
+ }
+}
diff --git a/gui/src/renderer/components/WireguardKeysStyles.tsx b/gui/src/renderer/components/WireguardKeysStyles.tsx
new file mode 100644
index 0000000000..8f94bdcca6
--- /dev/null
+++ b/gui/src/renderer/components/WireguardKeysStyles.tsx
@@ -0,0 +1,48 @@
+import { Styles } from 'reactxp';
+import { colors } from '../../config.json';
+
+export default {
+ wgkeys__container: Styles.createViewStyle({
+ flexDirection: 'column',
+ flex: 1,
+ }),
+ wgkeys__row: Styles.createViewStyle({
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingLeft: 24,
+ paddingRight: 24,
+ marginBottom: 24,
+ }),
+ wgkeys__row_label: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: '600',
+ lineHeight: 20,
+ letterSpacing: -0.2,
+ color: colors.white60,
+ marginBottom: 9,
+ }),
+ wgkeys__validity_row: Styles.createViewStyle({
+ paddingTop: 5,
+ }),
+ wgkeys__row_value: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 16,
+ lineHeight: 19,
+ fontWeight: '800',
+ color: colors.white,
+ }),
+ wgkeys__invalid_key: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 16,
+ fontWeight: '800',
+ color: colors.red,
+ }),
+ wgkeys__valid_key: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 16,
+ fontWeight: '600',
+ lineHeight: 20,
+ color: colors.green,
+ }),
+};
diff --git a/gui/src/renderer/containers/AdvancedSettingsPage.tsx b/gui/src/renderer/containers/AdvancedSettingsPage.tsx
index a8784a62e2..4dcb91b6b5 100644
--- a/gui/src/renderer/containers/AdvancedSettingsPage.tsx
+++ b/gui/src/renderer/containers/AdvancedSettingsPage.tsx
@@ -1,4 +1,4 @@
-import { goBack } from 'connected-react-router';
+import { goBack, push } from 'connected-react-router';
import log from 'electron-log';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
@@ -12,12 +12,14 @@ import { ISharedRouteProps } from '../routes';
const mapStateToProps = (state: IReduxState) => {
const protocolAndPort = mapRelaySettingsToProtocolAndPort(state.settings.relaySettings);
+ const enableWireguardKeysPage = process.platform === 'linux' || process.platform === 'darwin';
return {
enableIpv6: state.settings.enableIpv6,
blockWhenDisconnected: state.settings.blockWhenDisconnected,
mssfix: state.settings.openVpn.mssfix,
bridgeState: state.settings.bridgeState,
+ enableWireguardKeysPage,
...protocolAndPort,
};
};
@@ -38,7 +40,7 @@ const mapRelaySettingsToProtocolAndPort = (relaySettings: RelaySettingsRedux) =>
};
const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
- const history = bindActionCreators({ goBack }, dispatch);
+ const history = bindActionCreators({ push, goBack }, dispatch);
return {
onClose: () => {
history.goBack();
@@ -98,6 +100,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) =
log.error('Failed to update mssfix value', e.message);
}
},
+ onViewWireguardKeys: () => history.push('/settings/advanced/wireguard-keys'),
};
};
diff --git a/gui/src/renderer/containers/WireguardKeysPage.tsx b/gui/src/renderer/containers/WireguardKeysPage.tsx
new file mode 100644
index 0000000000..2fb185cb1d
--- /dev/null
+++ b/gui/src/renderer/containers/WireguardKeysPage.tsx
@@ -0,0 +1,27 @@
+import { goBack, push } from 'connected-react-router';
+import { shell } from 'electron';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { links } from '../../config.json';
+import WireguardKeys from '../components/WireguardKeys';
+import { IReduxState, ReduxDispatch } from '../redux/store';
+import { ISharedRouteProps } from '../routes';
+
+const mapStateToProps = (state: IReduxState, _props: ISharedRouteProps) => ({
+ keyState: state.settings.wireguardKeyState,
+ isOffline: state.connection.isBlocked,
+});
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
+ const history = bindActionCreators({ push, goBack }, dispatch);
+ return {
+ onGenerateKey: () => props.app.generateWireguardKey(),
+ onVerifyKey: (publicKey: string) => props.app.verifyWireguardKey(publicKey),
+ onVisitWebsiteKey: () => shell.openExternal(links.manageKeys),
+ onClose: () => history.goBack(),
+ };
+};
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(WireguardKeys);
diff --git a/gui/src/renderer/redux/settings/actions.ts b/gui/src/renderer/redux/settings/actions.ts
index 742923276d..6c87c18fa9 100644
--- a/gui/src/renderer/redux/settings/actions.ts
+++ b/gui/src/renderer/redux/settings/actions.ts
@@ -1,4 +1,4 @@
-import { BridgeState } from '../../../shared/daemon-rpc-types';
+import { BridgeState, KeygenEvent } from '../../../shared/daemon-rpc-types';
import { IGuiSettingsState } from '../../../shared/gui-settings-state';
import { IRelayLocationRedux, RelaySettingsRedux } from './reducers';
@@ -47,6 +47,31 @@ export interface IUpdateAutoStartAction {
autoStart: boolean;
}
+// Used to set wireguard key when accounts are changed.
+export interface IWireguardSetKey {
+ type: 'SET_WIREGUARD_KEY';
+ publicKey?: string;
+}
+
+export interface IWireguardGenerateKey {
+ type: 'GENERATE_WIREGUARD_KEY';
+}
+
+export interface IWireguardVerifyKey {
+ type: 'VERIFY_WIREGUARD_KEY';
+ publicKey: string;
+}
+
+export interface IWireguardKeygenEvent {
+ type: 'WIREGUARD_KEYGEN_EVENT';
+ event: KeygenEvent;
+}
+
+export interface IWireguardKeyVerifiedAction {
+ type: 'WIREGUARD_KEY_VERIFICATION_COMPLETE';
+ verified: boolean;
+}
+
export type SettingsAction =
| IUpdateGuiSettingsAction
| IUpdateRelayAction
@@ -56,7 +81,12 @@ export type SettingsAction =
| IUpdateBlockWhenDisconnectedAction
| IUpdateBridgeStateAction
| IUpdateOpenVpnMssfixAction
- | IUpdateAutoStartAction;
+ | IUpdateAutoStartAction
+ | IWireguardSetKey
+ | IWireguardVerifyKey
+ | IWireguardGenerateKey
+ | IWireguardKeygenEvent
+ | IWireguardKeyVerifiedAction;
function updateGuiSettings(guiSettings: IGuiSettingsState): IUpdateGuiSettingsAction {
return {
@@ -123,6 +153,40 @@ function updateAutoStart(autoStart: boolean): IUpdateAutoStartAction {
};
}
+function setWireguardKey(publicKey?: string): IWireguardSetKey {
+ return {
+ type: 'SET_WIREGUARD_KEY',
+ publicKey,
+ };
+}
+
+function setWireguardKeygenEvent(event: KeygenEvent): IWireguardKeygenEvent {
+ return {
+ type: 'WIREGUARD_KEYGEN_EVENT',
+ event,
+ };
+}
+
+function generateWireguardKey(): IWireguardGenerateKey {
+ return {
+ type: 'GENERATE_WIREGUARD_KEY',
+ };
+}
+
+function verifyWireguardKey(publicKey: string): IWireguardVerifyKey {
+ return {
+ type: 'VERIFY_WIREGUARD_KEY',
+ publicKey,
+ };
+}
+
+function completeWireguardKeyVerification(verified: boolean): IWireguardKeyVerifiedAction {
+ return {
+ type: 'WIREGUARD_KEY_VERIFICATION_COMPLETE',
+ verified,
+ };
+}
+
export default {
updateGuiSettings,
updateRelay,
@@ -133,4 +197,9 @@ export default {
updateBridgeState,
updateOpenVpnMssfix,
updateAutoStart,
+ setWireguardKey,
+ setWireguardKeygenEvent,
+ generateWireguardKey,
+ verifyWireguardKey,
+ completeWireguardKeyVerification,
};
diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts
index 6c652f887d..97a6a21ee4 100644
--- a/gui/src/renderer/redux/settings/reducers.ts
+++ b/gui/src/renderer/redux/settings/reducers.ts
@@ -1,4 +1,10 @@
-import { BridgeState, RelayLocation, RelayProtocol } from '../../../shared/daemon-rpc-types';
+import log from 'electron-log';
+import {
+ BridgeState,
+ KeygenEvent,
+ RelayLocation,
+ RelayProtocol,
+} from '../../../shared/daemon-rpc-types';
import { IGuiSettingsState } from '../../../shared/gui-settings-state';
import { ReduxAction } from '../store';
@@ -41,6 +47,41 @@ export interface IRelayLocationRedux {
cities: IRelayLocationCityRedux[];
}
+interface IWgKeySet {
+ type: 'key-set';
+ publicKey: string;
+ valid?: boolean;
+}
+
+interface IWgKeyNotSet {
+ type: 'key-not-set';
+}
+
+interface IWgTooManyKeys {
+ type: 'too-many-keys';
+}
+
+interface IWgKeyGenerationFailure {
+ type: 'generation-failure';
+}
+
+interface IWgKeyBeingGenerated {
+ type: 'being-generated';
+}
+
+interface IWgKeyBeingVerified {
+ type: 'being-verified';
+ publicKey: string;
+}
+
+export type WgKeyState =
+ | IWgKeySet
+ | IWgKeyNotSet
+ | IWgKeyGenerationFailure
+ | IWgTooManyKeys
+ | IWgKeyBeingVerified
+ | IWgKeyBeingGenerated;
+
export interface ISettingsReduxState {
autoStart: boolean;
guiSettings: IGuiSettingsState;
@@ -53,6 +94,7 @@ export interface ISettingsReduxState {
openVpn: {
mssfix?: number;
};
+ wireguardKeyState: WgKeyState;
}
const initialState: ISettingsReduxState = {
@@ -76,6 +118,9 @@ const initialState: ISettingsReduxState = {
bridgeState: 'auto',
blockWhenDisconnected: false,
openVpn: {},
+ wireguardKeyState: {
+ type: 'key-not-set',
+ },
};
export default function(
@@ -140,7 +185,77 @@ export default function(
bridgeState: action.bridgeState,
};
+ case 'SET_WIREGUARD_KEY':
+ return {
+ ...state,
+ wireguardKeyState: setWireguardKey(action.publicKey),
+ };
+ case 'WIREGUARD_KEYGEN_EVENT':
+ return {
+ ...state,
+ wireguardKeyState: setWireguardKeygenEvent(action.event),
+ };
+ case 'WIREGUARD_KEY_VERIFICATION_COMPLETE':
+ return {
+ ...state,
+ wireguardKeyState: applyKeyVerification(state.wireguardKeyState, action.verified),
+ };
+ case 'VERIFY_WIREGUARD_KEY':
+ return {
+ ...state,
+ wireguardKeyState: { type: 'being-verified', publicKey: action.publicKey },
+ };
+
+ case 'GENERATE_WIREGUARD_KEY':
+ return {
+ ...state,
+ wireguardKeyState: { type: 'being-generated' },
+ };
+
+ default:
+ return state;
+ }
+}
+
+function setWireguardKey(publicKey?: string): WgKeyState {
+ if (publicKey) {
+ return {
+ type: 'key-set',
+ publicKey,
+ };
+ } else {
+ return {
+ type: 'key-not-set',
+ };
+ }
+}
+
+function setWireguardKeygenEvent(keygenEvent: KeygenEvent): WgKeyState {
+ switch (keygenEvent) {
+ case 'too_many_keys':
+ return { type: 'too-many-keys' };
+ case 'generation_failure':
+ return { type: 'generation-failure' };
+ default:
+ return {
+ type: 'key-set',
+ publicKey: keygenEvent.newKey,
+ valid: true,
+ };
+ }
+}
+
+function applyKeyVerification(state: WgKeyState, verified: boolean): WgKeyState {
+ switch (state.type) {
+ case 'being-verified':
+ return {
+ ...state,
+ type: 'key-set',
+ valid: verified,
+ };
+ // drop the verification event if the key wasn't being verified.
default:
+ log.error(`Received key verification event when key wasn't being verified`);
return state;
}
}
diff --git a/gui/src/renderer/routes.tsx b/gui/src/renderer/routes.tsx
index ae694cefbc..814eeae31e 100644
--- a/gui/src/renderer/routes.tsx
+++ b/gui/src/renderer/routes.tsx
@@ -12,6 +12,7 @@ import PreferencesPage from './containers/PreferencesPage';
import SelectLocationPage from './containers/SelectLocationPage';
import SettingsPage from './containers/SettingsPage';
import SupportPage from './containers/SupportPage';
+import WireguardKeysPage from './containers/WireguardKeysPage';
import { getTransitionProps } from './transitions';
export interface ISharedRouteProps {
@@ -90,6 +91,11 @@ class AppRoutes extends React.Component<IAppRoutesProps, IAppRoutesState> {
path="/settings/advanced"
component={AdvancedSettingsPage}
/>
+ <CustomRoute
+ exact={true}
+ path="/settings/advanced/wireguard-keys"
+ component={WireguardKeysPage}
+ />
<CustomRoute exact={true} path="/settings/support" component={SupportPage} />
<CustomRoute exact={true} path="/select-location" component={SelectLocationPage} />
</Switch>
diff --git a/gui/src/shared/ipc-event-channel.ts b/gui/src/shared/ipc-event-channel.ts
index 207e509779..2bb594f5e6 100644
--- a/gui/src/shared/ipc-event-channel.ts
+++ b/gui/src/shared/ipc-event-channel.ts
@@ -12,6 +12,7 @@ import {
ILocation,
IRelayList,
ISettings,
+ KeygenEvent,
RelaySettingsUpdate,
TunnelState,
} from './daemon-rpc-types';
@@ -28,6 +29,7 @@ export interface IAppStateSnapshot {
currentVersion: ICurrentAppVersionInfo;
upgradeVersion: IAppUpgradeInfo;
guiSettings: IGuiSettingsState;
+ wireguardPublicKey?: string;
}
interface ISender<T> {
@@ -112,6 +114,18 @@ interface IAutoStartHandlers extends ISender<boolean> {
handleSet(fn: (value: boolean) => Promise<void>): void;
}
+interface IWireguardKeyMethods extends IReceiver<string | undefined> {
+ listenKeygenEvents(fn: (event: KeygenEvent) => void): void;
+ generateKey(): Promise<KeygenEvent>;
+ verifyKey(): Promise<boolean>;
+}
+
+interface IWireguardKeyHandlers extends ISender<string | undefined> {
+ notifyKeygenEvent(webContents: WebContents, event: KeygenEvent): void;
+ handleGenerateKey(fn: () => Promise<KeygenEvent>): void;
+ handleVerifyKey(fn: () => Promise<boolean>): void;
+}
+
/// Events names
const DAEMON_CONNECTED = 'daemon-connected';
@@ -152,6 +166,11 @@ const GET_ACCOUNT_DATA = 'get-account-data';
const AUTO_START_CHANGED = 'auto-start-changed';
const SET_AUTO_START = 'set-auto-start';
+const WIREGUARD_KEY_SET = 'wireguard-key-change-event';
+const WIREGUARD_KEYGEN_EVENT = 'wireguard-keygen-event';
+const GENERATE_WIREGUARD_KEY = 'generate-wireguard-key';
+const VERIFY_WIREGUARD_KEY = 'verify-wireguard-key';
+
/// Typed IPC event channel
///
/// Static methods are meant to be provide the way to send the events from a renderer process, while
@@ -228,6 +247,13 @@ export class IpcRendererEventChannel {
listen: listen(ACCOUNT_HISTORY_CHANGED),
removeItem: requestSender(REMOVE_ACCOUNT_HISTORY_ITEM),
};
+
+ public static wireguardKeys: IWireguardKeyMethods = {
+ listen: listen(WIREGUARD_KEY_SET),
+ listenKeygenEvents: listen(WIREGUARD_KEYGEN_EVENT),
+ generateKey: requestSender(GENERATE_WIREGUARD_KEY),
+ verifyKey: requestSender(VERIFY_WIREGUARD_KEY),
+ };
}
export class IpcMainEventChannel {
@@ -302,6 +328,13 @@ export class IpcMainEventChannel {
notify: sender<AccountToken[]>(ACCOUNT_HISTORY_CHANGED),
handleRemoveItem: requestHandler(REMOVE_ACCOUNT_HISTORY_ITEM),
};
+
+ public static wireguardKeys: IWireguardKeyHandlers = {
+ notify: sender<string | undefined>(WIREGUARD_KEY_SET),
+ notifyKeygenEvent: sender<KeygenEvent>(WIREGUARD_KEYGEN_EVENT),
+ handleGenerateKey: requestHandler(GENERATE_WIREGUARD_KEY),
+ handleVerifyKey: requestHandler(VERIFY_WIREGUARD_KEY),
+ };
}
function listen<T>(event: string): (fn: (value: T) => void) => void {