summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls Piņķis <emils@mullvad.net>2019-09-05 10:53:06 +0100
committerEmīls Piņķis <emils@mullvad.net>2019-09-05 10:53:06 +0100
commit0bccc7ec2d619c6133e78465bc4c1a9afffb7638 (patch)
tree07c0a3be4e17961736c36c60eb6fea994ca6c08b
parent14be4d9a9a79f37e6f27340f4d9f6aed3bd46a67 (diff)
parent6ebbe56e051c465273ce8f2c16851eb57d222cd4 (diff)
downloadmullvadvpn-0bccc7ec2d619c6133e78465bc4c1a9afffb7638.tar.xz
mullvadvpn-0bccc7ec2d619c6133e78465bc4c1a9afffb7638.zip
Merge branch 'add-key-rotation-rpc'
-rw-r--r--CHANGELOG.md6
-rw-r--r--gui/src/main/daemon-rpc.ts15
-rw-r--r--gui/src/main/index.ts5
-rw-r--r--gui/src/renderer/app.tsx24
-rw-r--r--gui/src/renderer/components/WireguardKeys.tsx156
-rw-r--r--gui/src/renderer/components/WireguardKeysStyles.tsx1
-rw-r--r--gui/src/renderer/containers/WireguardKeysPage.tsx9
-rw-r--r--gui/src/renderer/redux/settings/actions.ts41
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts72
-rw-r--r--gui/src/shared/daemon-rpc-types.ts10
-rw-r--r--gui/src/shared/ipc-event-channel.ts9
-rw-r--r--mullvad-daemon/src/account_history.rs6
-rw-r--r--mullvad-daemon/src/lib.rs24
-rw-r--r--mullvad-daemon/src/management_interface.rs8
-rw-r--r--mullvad-daemon/src/wireguard.rs55
-rw-r--r--mullvad-jni/src/daemon_interface.rs3
-rw-r--r--mullvad-jni/src/into_java.rs2
-rw-r--r--mullvad-jni/src/lib.rs2
-rw-r--r--mullvad-rpc/src/lib.rs6
-rw-r--r--mullvad-types/src/wireguard.rs24
20 files changed, 365 insertions, 113 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89b9446955..c18b32b679 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,13 +23,19 @@ Line wrap the file at 100 chars. Th
## [Unreleased]
+### Added
+- Add ability to replace the WireGuard key with a new one. Allows manual key rotation.
+- Show age of currently set WireGuard key.
+
### Changed
- Decreased default MTU for WireGuard to 1380 to improve performance over 4G
- WireGuard key page now shows a label explaining why buttons are disabled when in a blocked state
+- WireGuard key generation will try to replace old key if one exists.
### Fixed
- Fix old settings deserialization to allow migrating settings from versions older than 2019.6.
- Fix various small issues in GUI<->daemon communication.
+- Make GUI WireGuard key verification resilient to failure.
#### macOS
- Unregister the app properly from the OS when running the bundled `uninstall.sh` script.
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 0a55833731..2d4141304e 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -8,6 +8,7 @@ import {
ILocation,
IRelayList,
ISettings,
+ IWireguardPublicKey,
KeygenEvent,
RelaySettingsUpdate,
TunnelState,
@@ -337,10 +338,18 @@ const settingsSchema = partialObject({
tunnel_options: tunnelOptionsSchema,
});
+const wireguardPublicKey = object({
+ key: string,
+ created: string,
+});
+
const keygenEventSchema = oneOf(
enumeration('too_many_keys', 'generation_failure'),
object({
- new_key: string,
+ new_key: object({
+ key: string,
+ created: string,
+ }),
}),
);
@@ -567,10 +576,10 @@ export class DaemonRpc {
}
}
- public async getWireguardKey(): Promise<string | undefined> {
+ public async getWireguardKey(): Promise<IWireguardPublicKey | undefined> {
const response = await this.transport.send('get_wireguard_key');
try {
- return validate(maybe(string), response) || undefined;
+ return validate(maybe(wireguardPublicKey), response) || undefined;
} catch (error) {
throw new ResponseParseError('Invalid response from get_wireguard_key');
}
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index efe76bdfab..a0694e814c 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -14,6 +14,7 @@ import {
IRelayList,
IRelayListHostname,
ISettings,
+ IWireguardPublicKey,
KeygenEvent,
RelaySettings,
RelaySettingsUpdate,
@@ -143,7 +144,7 @@ class ApplicationMain {
// The UI locale which is set once from onReady handler
private locale = 'en';
- private wireguardPublicKey?: string;
+ private wireguardPublicKey?: IWireguardPublicKey;
private accountDataCache = new AccountDataCache(
(accountToken) => {
@@ -560,7 +561,7 @@ class ApplicationMain {
}
}
- private setWireguardKey(wireguardKey?: string) {
+ private setWireguardKey(wireguardKey?: IWireguardPublicKey) {
this.wireguardPublicKey = wireguardKey;
if (this.windowController) {
IpcMainEventChannel.wireguardKeys.notify(this.windowController.webContents, wireguardKey);
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 094b4f376d..b08743dab4 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -16,6 +16,7 @@ import AppRoutes from './routes';
import accountActions from './redux/account/actions';
import connectionActions from './redux/connection/actions';
import settingsActions from './redux/settings/actions';
+import { IWgKey } from './redux/settings/reducers';
import configureStore from './redux/store';
import userInterfaceActions from './redux/userinterface/actions';
import versionActions from './redux/version/actions';
@@ -33,6 +34,7 @@ import {
ILocation,
IRelayList,
ISettings,
+ IWireguardPublicKey,
KeygenEvent,
liftConstraint,
RelaySettings,
@@ -129,7 +131,7 @@ export default class AppRenderer {
this.storeAutoStart(autoStart);
});
- IpcRendererEventChannel.wireguardKeys.listen((publicKey?: string) => {
+ IpcRendererEventChannel.wireguardKeys.listen((publicKey?: IWireguardPublicKey) => {
this.setWireguardPublicKey(publicKey);
});
@@ -303,11 +305,16 @@ export default class AppRenderer {
IpcRendererEventChannel.guiSettings.setMonochromaticIcon(monochromaticIcon);
}
- public async verifyWireguardKey(publicKey: string) {
+ public async verifyWireguardKey(publicKey: IWgKey) {
const actions = this.reduxActions;
actions.settings.verifyWireguardKey(publicKey);
- const valid = await IpcRendererEventChannel.wireguardKeys.verifyKey();
- actions.settings.completeWireguardKeyVerification(valid);
+ try {
+ const valid = await IpcRendererEventChannel.wireguardKeys.verifyKey();
+ actions.settings.completeWireguardKeyVerification(valid);
+ } catch (error) {
+ log.error(`Failed to verify WireGuard key - ${error.message}`);
+ actions.settings.completeWireguardKeyVerification(undefined);
+ }
}
public async generateWireguardKey() {
@@ -317,6 +324,13 @@ export default class AppRenderer {
actions.settings.setWireguardKeygenEvent(keygenEvent);
}
+ public async replaceWireguardKey(oldKey: IWgKey) {
+ const actions = this.reduxActions;
+ actions.settings.replaceWireguardKey(oldKey);
+ const keygenEvent = await IpcRendererEventChannel.wireguardKeys.generateKey();
+ actions.settings.setWireguardKeygenEvent(keygenEvent);
+ }
+
private setRelaySettings(relaySettings: RelaySettings) {
const actions = this.reduxActions;
@@ -554,7 +568,7 @@ export default class AppRenderer {
this.reduxActions.settings.updateAutoStart(autoStart);
}
- private setWireguardPublicKey(publicKey?: string) {
+ private setWireguardPublicKey(publicKey?: IWireguardPublicKey) {
this.reduxActions.settings.setWireguardKey(publicKey);
}
}
diff --git a/gui/src/renderer/components/WireguardKeys.tsx b/gui/src/renderer/components/WireguardKeys.tsx
index d66ef68cf2..9ea386ec91 100644
--- a/gui/src/renderer/components/WireguardKeys.tsx
+++ b/gui/src/renderer/components/WireguardKeys.tsx
@@ -1,7 +1,10 @@
+import log from 'electron-log';
+import moment from 'moment';
import * as React from 'react';
import { Component, Text, View } from 'reactxp';
+import { sprintf } from 'sprintf-js';
import { messages } from '../../shared/gettext';
-import { WgKeyState } from '../redux/settings/reducers';
+import { IWgKey, WgKeyState } from '../redux/settings/reducers';
import * as AppButton from './AppButton';
import ImageView from './ImageView';
import { Container, Layout } from './Layout';
@@ -12,11 +15,13 @@ import styles from './WireguardKeysStyles';
export interface IProps {
keyState: WgKeyState;
isOffline: boolean;
+ locale: string;
+ onClose: () => void;
onGenerateKey: () => void;
- onVerifyKey: (publicKey: string) => void;
+ onReplaceKey: (old: IWgKey) => void;
+ onVerifyKey: (publicKey: IWgKey) => void;
onVisitWebsiteKey: () => void;
- onClose: () => void;
}
export default class WireguardKeys extends Component<IProps> {
@@ -46,11 +51,24 @@ export default class WireguardKeys extends Component<IProps> {
<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__row_value}>{this.getKeyText()}</View>
+ <Text style={styles.wgkeys__row_label}>
+ {messages.pgettext('wireguard-keys', 'Key generated')}
+ </Text>
+ <Text style={styles.wgkeys__row_value}>{this.ageOfKeyString()}</Text>
<View style={styles.wgkeys__validity_row}>{this.keyValidityLabel()}</View>
</View>
- <View style={styles.wgkeys__row}>{this.getActionButton()}</View>
+ <View style={styles.wgkeys__row}>{this.getGenerateButton()}</View>
+ <View style={styles.wgkeys__row}>
+ <AppButton.GreenButton
+ disabled={this.isVerifyButtonDisabled()}
+ onPress={this.getOnVerifyKeyCb()}>
+ <AppButton.Label>
+ {messages.pgettext('wireguard-key-view', 'Verify key')}
+ </AppButton.Label>
+ </AppButton.GreenButton>
+ </View>
<View style={styles.wgkeys__row}>
<AppButton.GreenButton
disabled={this.props.isOffline}
@@ -68,34 +86,50 @@ export default class WireguardKeys extends Component<IProps> {
);
}
- /// Action button can either generate or verify a key
- private getActionButton() {
+ private isVerifyButtonDisabled(): boolean {
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;
- }
+ return false || this.props.isOffline;
+ default:
+ return true;
+ }
+ }
- const verificationCallback = () => this.props.onVerifyKey(publicKey);
+ private getOnVerifyKeyCb() {
+ return () => {
+ switch (this.props.keyState.type) {
+ case 'key-set':
+ const key = this.props.keyState.key;
+ this.props.onVerifyKey(key);
+ break;
+ default:
+ log.error(`onVerifyKey called from invalid state - ${this.props.keyState.type}`);
+ }
+ };
+ }
- return (
- <AppButton.GreenButton disabled={this.props.isOffline} onPress={verificationCallback}>
- <AppButton.Label>
- {messages.pgettext('wireguard-key-view', 'Verify key')}
- </AppButton.Label>
- </AppButton.GreenButton>
- );
+ /// Action button can either generate or verify a key
+ private getGenerateButton() {
+ const generateText = messages.pgettext('wireguard-key-view', 'Generate key');
+ const regenerateText = messages.pgettext('wireguard-key-view', 'Regenerate key');
+ let buttonText = generateText;
+ let generateKey = this.props.onGenerateKey;
+ switch (this.props.keyState.type) {
+ case 'key-set':
+ buttonText = regenerateText;
+ const key = this.props.keyState.key;
+ generateKey = () => this.props.onReplaceKey(key);
+ break;
case 'being-verified':
- return this.busyButton(messages.pgettext('wireguard-key-view', 'Verifying key'));
+ return this.busyButton(regenerateText);
+ case 'being-replaced':
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 disabled={this.props.isOffline} onPress={generateKey}>
+ <AppButton.Label>{buttonText}</AppButton.Label>
</AppButton.GreenButton>
);
}
@@ -109,30 +143,26 @@ export default class WireguardKeys extends Component<IProps> {
);
}
- private getKeyRow() {
+ private getKeyText() {
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}>
+ <View title={this.props.keyState.key.publicKey}>
<Text style={styles.wgkeys__row_value}>
- {this.props.keyState.publicKey.substring(0, 20) + '...'}
+ {this.props.keyState.key.publicKey.substring(0, 20) + '...'}
</Text>
</View>
);
+ case 'being-replaced':
case 'being-generated':
- return <ImageView source="icon-spinner" height={25} width={25} />;
+ return <ImageView source="icon-spinner" height={19} width={19} />;
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')}
+ {this.formatKeygenFailure(this.props.keyState.type)}
</Text>
);
default:
@@ -147,23 +177,75 @@ export default class WireguardKeys extends Component<IProps> {
private keyValidityLabel() {
switch (this.props.keyState.type) {
case 'being-verified':
- return <ImageView source="icon-spinner" height={25} width={25} />;
+ return <ImageView source="icon-spinner" height={20} width={20} />;
case 'key-set':
- if (this.props.keyState.valid === true) {
+ const key = this.props.keyState.key;
+ if (key.valid === true) {
return (
<Text style={styles.wgkeys__valid_key}>
{messages.pgettext('account-view', 'Key is valid')}
</Text>
);
- } else if (this.props.keyState.valid === false) {
+ } else if (key.valid === false) {
return (
<Text style={styles.wgkeys__invalid_key}>
{messages.pgettext('wireguard-key-view', 'Key is invalid')}
</Text>
);
+ } else if (key.replacementFailure) {
+ let failure = '';
+ switch (key.replacementFailure) {
+ case 'too_many_keys':
+ failure = this.formatKeygenFailure('too-many-keys');
+ break;
+ case 'generation_failure':
+ failure = this.formatKeygenFailure('generation-failure');
+ break;
+ }
+
+ const failureMessage = sprintf(
+ messages.pgettext('wireguard-key-view', 'Failed to replace key - %(failure)s'),
+ { failure },
+ );
+ return <Text style={styles.wgkeys__invalid_key}>{failureMessage}</Text>;
+ } else if (key.verificationFailed) {
+ return (
+ <Text style={styles.wgkeys__invalid_key}>
+ {messages.pgettext('wireguard-key-view', 'Key verification failed')}
+ </Text>
+ );
}
+
+ default:
+ return (
+ // Placeholder to take up the same amount of space as the validity text/spinner
+ <View style={{ marginBottom: 20 }} />
+ );
+ }
+ }
+
+ private ageOfKeyString(): string {
+ let keyCreatedSince = '-';
+ switch (this.props.keyState.type) {
+ case 'key-set':
+ case 'being-verified':
+ keyCreatedSince = moment(this.props.keyState.key.created)
+ .locale(this.props.locale)
+ .fromNow();
+ break;
+ }
+
+ return keyCreatedSince;
+ }
+
+ private formatKeygenFailure(failure: 'too-many-keys' | 'generation-failure'): string {
+ switch (failure) {
+ case 'too-many-keys':
+ return messages.pgettext('wireguard-key-view', 'Account has too many keys already');
+ case 'generation-failure':
+ return messages.pgettext('wireguard-key-view', 'Failed to generate a key');
default:
- return '';
+ return failure;
}
}
diff --git a/gui/src/renderer/components/WireguardKeysStyles.tsx b/gui/src/renderer/components/WireguardKeysStyles.tsx
index 2172b79dae..ec3fd6cf2f 100644
--- a/gui/src/renderer/components/WireguardKeysStyles.tsx
+++ b/gui/src/renderer/components/WireguardKeysStyles.tsx
@@ -24,7 +24,6 @@ export default {
lineHeight: 20,
letterSpacing: -0.2,
color: colors.white60,
- marginBottom: 9,
}),
wgkeys__validity_row: Styles.createViewStyle({
paddingTop: 5,
diff --git a/gui/src/renderer/containers/WireguardKeysPage.tsx b/gui/src/renderer/containers/WireguardKeysPage.tsx
index 2fb185cb1d..813526ff29 100644
--- a/gui/src/renderer/containers/WireguardKeysPage.tsx
+++ b/gui/src/renderer/containers/WireguardKeysPage.tsx
@@ -4,20 +4,23 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { links } from '../../config.json';
import WireguardKeys from '../components/WireguardKeys';
+import { IWgKey } from '../redux/settings/reducers';
import { IReduxState, ReduxDispatch } from '../redux/store';
import { ISharedRouteProps } from '../routes';
-const mapStateToProps = (state: IReduxState, _props: ISharedRouteProps) => ({
+const mapStateToProps = (state: IReduxState, props: ISharedRouteProps) => ({
keyState: state.settings.wireguardKeyState,
isOffline: state.connection.isBlocked,
+ locale: props.locale,
});
const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => {
const history = bindActionCreators({ push, goBack }, dispatch);
return {
+ onClose: () => history.goBack(),
onGenerateKey: () => props.app.generateWireguardKey(),
- onVerifyKey: (publicKey: string) => props.app.verifyWireguardKey(publicKey),
+ onReplaceKey: (oldKey: IWgKey) => props.app.replaceWireguardKey(oldKey),
+ onVerifyKey: (publicKey: IWgKey) => props.app.verifyWireguardKey(publicKey),
onVisitWebsiteKey: () => shell.openExternal(links.manageKeys),
- onClose: () => history.goBack(),
};
};
diff --git a/gui/src/renderer/redux/settings/actions.ts b/gui/src/renderer/redux/settings/actions.ts
index 6c87c18fa9..a7c766e7fd 100644
--- a/gui/src/renderer/redux/settings/actions.ts
+++ b/gui/src/renderer/redux/settings/actions.ts
@@ -1,6 +1,6 @@
-import { BridgeState, KeygenEvent } from '../../../shared/daemon-rpc-types';
+import { BridgeState, IWireguardPublicKey, KeygenEvent } from '../../../shared/daemon-rpc-types';
import { IGuiSettingsState } from '../../../shared/gui-settings-state';
-import { IRelayLocationRedux, RelaySettingsRedux } from './reducers';
+import { IRelayLocationRedux, IWgKey, RelaySettingsRedux } from './reducers';
export interface IUpdateGuiSettingsAction {
type: 'UPDATE_GUI_SETTINGS';
@@ -50,16 +50,21 @@ export interface IUpdateAutoStartAction {
// Used to set wireguard key when accounts are changed.
export interface IWireguardSetKey {
type: 'SET_WIREGUARD_KEY';
- publicKey?: string;
+ key?: IWgKey;
}
export interface IWireguardGenerateKey {
type: 'GENERATE_WIREGUARD_KEY';
}
+export interface IWireguardReplaceKey {
+ type: 'REPLACE_WIREGUARD_KEY';
+ oldKey: IWgKey;
+}
+
export interface IWireguardVerifyKey {
type: 'VERIFY_WIREGUARD_KEY';
- publicKey: string;
+ key: IWgKey;
}
export interface IWireguardKeygenEvent {
@@ -69,7 +74,7 @@ export interface IWireguardKeygenEvent {
export interface IWireguardKeyVerifiedAction {
type: 'WIREGUARD_KEY_VERIFICATION_COMPLETE';
- verified: boolean;
+ verified?: boolean;
}
export type SettingsAction =
@@ -85,6 +90,7 @@ export type SettingsAction =
| IWireguardSetKey
| IWireguardVerifyKey
| IWireguardGenerateKey
+ | IWireguardReplaceKey
| IWireguardKeygenEvent
| IWireguardKeyVerifiedAction;
@@ -153,10 +159,17 @@ function updateAutoStart(autoStart: boolean): IUpdateAutoStartAction {
};
}
-function setWireguardKey(publicKey?: string): IWireguardSetKey {
+function setWireguardKey(publicKey?: IWireguardPublicKey): IWireguardSetKey {
+ const key = publicKey
+ ? {
+ publicKey: publicKey.key,
+ created: publicKey.created,
+ valid: undefined,
+ }
+ : undefined;
return {
type: 'SET_WIREGUARD_KEY',
- publicKey,
+ key,
};
}
@@ -173,14 +186,21 @@ function generateWireguardKey(): IWireguardGenerateKey {
};
}
-function verifyWireguardKey(publicKey: string): IWireguardVerifyKey {
+function replaceWireguardKey(oldKey: IWgKey): IWireguardReplaceKey {
+ return {
+ type: 'REPLACE_WIREGUARD_KEY',
+ oldKey,
+ };
+}
+
+function verifyWireguardKey(key: IWgKey): IWireguardVerifyKey {
return {
type: 'VERIFY_WIREGUARD_KEY',
- publicKey,
+ key,
};
}
-function completeWireguardKeyVerification(verified: boolean): IWireguardKeyVerifiedAction {
+function completeWireguardKeyVerification(verified?: boolean): IWireguardKeyVerifiedAction {
return {
type: 'WIREGUARD_KEY_VERIFICATION_COMPLETE',
verified,
@@ -200,6 +220,7 @@ export default {
setWireguardKey,
setWireguardKeygenEvent,
generateWireguardKey,
+ replaceWireguardKey,
verifyWireguardKey,
completeWireguardKeyVerification,
};
diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts
index e90022062c..5d7f37fb50 100644
--- a/gui/src/renderer/redux/settings/reducers.ts
+++ b/gui/src/renderer/redux/settings/reducers.ts
@@ -54,10 +54,17 @@ export interface IRelayLocationRedux {
cities: IRelayLocationCityRedux[];
}
-interface IWgKeySet {
- type: 'key-set';
+export interface IWgKey {
publicKey: string;
+ created: string;
valid?: boolean;
+ replacementFailure?: KeygenEvent;
+ verificationFailed?: boolean;
+}
+
+interface IWgKeySet {
+ type: 'key-set';
+ key: IWgKey;
}
interface IWgKeyNotSet {
@@ -76,9 +83,14 @@ interface IWgKeyBeingGenerated {
type: 'being-generated';
}
+interface IWgKeyBeingReplaced {
+ type: 'being-replaced';
+ oldKey: IWgKey;
+}
+
interface IWgKeyBeingVerified {
type: 'being-verified';
- publicKey: string;
+ key: IWgKey;
}
export type WgKeyState =
@@ -87,6 +99,7 @@ export type WgKeyState =
| IWgKeyGenerationFailure
| IWgTooManyKeys
| IWgKeyBeingVerified
+ | IWgKeyBeingReplaced
| IWgKeyBeingGenerated;
export interface ISettingsReduxState {
@@ -199,12 +212,12 @@ export default function(
case 'SET_WIREGUARD_KEY':
return {
...state,
- wireguardKeyState: setWireguardKey(action.publicKey),
+ wireguardKeyState: setWireguardKey(action.key),
};
case 'WIREGUARD_KEYGEN_EVENT':
return {
...state,
- wireguardKeyState: setWireguardKeygenEvent(action.event),
+ wireguardKeyState: setWireguardKeygenEvent(state, action.event),
};
case 'WIREGUARD_KEY_VERIFICATION_COMPLETE':
return {
@@ -214,7 +227,7 @@ export default function(
case 'VERIFY_WIREGUARD_KEY':
return {
...state,
- wireguardKeyState: { type: 'being-verified', publicKey: action.publicKey },
+ wireguardKeyState: { type: 'being-verified', key: action.key },
};
case 'GENERATE_WIREGUARD_KEY':
@@ -223,16 +236,22 @@ export default function(
wireguardKeyState: { type: 'being-generated' },
};
+ case 'REPLACE_WIREGUARD_KEY':
+ return {
+ ...state,
+ wireguardKeyState: { type: 'being-replaced', oldKey: action.oldKey },
+ };
+
default:
return state;
}
}
-function setWireguardKey(publicKey?: string): WgKeyState {
- if (publicKey) {
+function setWireguardKey(key?: IWgKey): WgKeyState {
+ if (key) {
return {
type: 'key-set',
- publicKey,
+ key,
};
} else {
return {
@@ -241,7 +260,23 @@ function setWireguardKey(publicKey?: string): WgKeyState {
}
}
-function setWireguardKeygenEvent(keygenEvent: KeygenEvent): WgKeyState {
+function setWireguardKeygenEvent(state: ISettingsReduxState, keygenEvent: KeygenEvent): WgKeyState {
+ const oldKeyState = state.wireguardKeyState;
+ if (oldKeyState.type === 'being-replaced') {
+ switch (keygenEvent) {
+ case 'too_many_keys':
+ case 'generation_failure':
+ return {
+ type: 'key-set',
+ key: {
+ ...oldKeyState.oldKey,
+ replacementFailure: keygenEvent,
+ },
+ };
+ default:
+ break;
+ }
+ }
switch (keygenEvent) {
case 'too_many_keys':
return { type: 'too-many-keys' };
@@ -250,19 +285,26 @@ function setWireguardKeygenEvent(keygenEvent: KeygenEvent): WgKeyState {
default:
return {
type: 'key-set',
- publicKey: keygenEvent.newKey,
- valid: true,
+ key: {
+ publicKey: keygenEvent.newKey.key,
+ created: keygenEvent.newKey.created,
+ valid: undefined,
+ },
};
}
}
-function applyKeyVerification(state: WgKeyState, verified: boolean): WgKeyState {
+function applyKeyVerification(state: WgKeyState, verified?: boolean): WgKeyState {
+ const verificationFailed = verified === undefined ? true : undefined;
switch (state.type) {
case 'being-verified':
return {
- ...state,
type: 'key-set',
- valid: verified,
+ key: {
+ ...state.key,
+ valid: verified,
+ verificationFailed,
+ },
};
// drop the verification event if the key wasn't being verified.
default:
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index f88cd29404..0f73586275 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -295,10 +295,16 @@ export interface ISettings {
bridgeState: BridgeState;
}
-export type KeygenEvent = INewWireguardKey | 'too_many_keys' | 'generation_failure';
+export type KeygenEvent = INewWireguardKey | KeygenFailure;
+export type KeygenFailure = 'too_many_keys' | 'generation_failure';
export interface INewWireguardKey {
- newKey: string;
+ newKey: IWireguardPublicKey;
+}
+
+export interface IWireguardPublicKey {
+ key: string;
+ created: string;
}
export type BridgeState = 'auto' | 'on' | 'off';
diff --git a/gui/src/shared/ipc-event-channel.ts b/gui/src/shared/ipc-event-channel.ts
index 8283cf43dc..7bdeb6e759 100644
--- a/gui/src/shared/ipc-event-channel.ts
+++ b/gui/src/shared/ipc-event-channel.ts
@@ -13,6 +13,7 @@ import {
ILocation,
IRelayList,
ISettings,
+ IWireguardPublicKey,
KeygenEvent,
RelaySettingsUpdate,
TunnelState,
@@ -31,7 +32,7 @@ export interface IAppStateSnapshot {
currentVersion: ICurrentAppVersionInfo;
upgradeVersion: IAppUpgradeInfo;
guiSettings: IGuiSettingsState;
- wireguardPublicKey?: string;
+ wireguardPublicKey?: IWireguardPublicKey;
}
interface ISender<T> {
@@ -114,13 +115,13 @@ interface IAutoStartHandlers extends ISender<boolean> {
handleSet(fn: (value: boolean) => Promise<void>): void;
}
-interface IWireguardKeyMethods extends IReceiver<string | undefined> {
+interface IWireguardKeyMethods extends IReceiver<IWireguardPublicKey | undefined> {
listenKeygenEvents(fn: (event: KeygenEvent) => void): void;
generateKey(): Promise<KeygenEvent>;
verifyKey(): Promise<boolean>;
}
-interface IWireguardKeyHandlers extends ISender<string | undefined> {
+interface IWireguardKeyHandlers extends ISender<IWireguardPublicKey | undefined> {
notifyKeygenEvent(webContents: WebContents, event: KeygenEvent): void;
handleGenerateKey(fn: () => Promise<KeygenEvent>): void;
handleVerifyKey(fn: () => Promise<boolean>): void;
@@ -340,7 +341,7 @@ export class IpcMainEventChannel {
};
public static wireguardKeys: IWireguardKeyHandlers = {
- notify: sender<string | undefined>(WIREGUARD_KEY_SET),
+ notify: sender<IWireguardPublicKey | undefined>(WIREGUARD_KEY_SET),
notifyKeygenEvent: sender<KeygenEvent>(WIREGUARD_KEYGEN_EVENT),
handleGenerateKey: requestHandler(GENERATE_WIREGUARD_KEY),
handleVerifyKey: requestHandler(VERIFY_WIREGUARD_KEY),
diff --git a/mullvad-daemon/src/account_history.rs b/mullvad-daemon/src/account_history.rs
index 8455c060d2..7ba5894190 100644
--- a/mullvad-daemon/src/account_history.rs
+++ b/mullvad-daemon/src/account_history.rs
@@ -72,7 +72,11 @@ impl AccountHistory {
Ok(accounts) => accounts,
};
let file = io::BufWriter::new(reader.into_inner());
- Ok(AccountHistory { file, accounts })
+ let mut history = AccountHistory { file, accounts };
+ if let Err(e) = history.save_to_disk() {
+ log::error!("Failed to save account cache after opening it: {}", e);
+ }
+ Ok(history)
}
fn try_old_format(reader: &mut io::BufReader<fs::File>) -> Result<Vec<AccountToken>> {
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index eed31fa2fc..12b7919fd0 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -795,7 +795,7 @@ where
match result {
Ok(data) => {
- let public_key = data.private_key.public_key();
+ let public_key = data.get_public_key();
let mut account_entry = self
.account_history
.get(&account)
@@ -1251,12 +1251,18 @@ where
})
})?;
- match self
- .wireguard_key_manager
- .generate_key_sync(account_token.clone())
- {
+ let gen_result = match &account_entry.wireguard {
+ Some(wireguard_data) => self
+ .wireguard_key_manager
+ .replace_key(account_token.clone(), wireguard_data.get_public_key()),
+ None => self
+ .wireguard_key_manager
+ .generate_key_sync(account_token.clone()),
+ };
+
+ match gen_result {
Ok(new_data) => {
- let public_key = new_data.private_key.public_key();
+ let public_key = new_data.get_public_key();
account_entry.wireguard = Some(new_data.clone());
self.account_history.insert(account_entry).map_err(|e| {
format!("Failed to add new wireguard key to account data: {}", e)
@@ -1285,11 +1291,7 @@ where
.settings
.get_account_token()
.and_then(|account| self.account_history.get(&account).ok()?)
- .and_then(|account_entry| {
- account_entry
- .wireguard
- .map(|wg| wg.private_key.public_key())
- });
+ .and_then(|account_entry| account_entry.wireguard.map(|wg| wg.get_public_key()));
Self::oneshot_send(tx, key, "get_wireguard_key response");
}
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index 0940780ea3..e1b74f6185 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -19,7 +19,7 @@ use mullvad_types::{
relay_list::RelayList,
settings::{self, Settings},
states::{TargetState, TunnelState},
- version, DaemonEvent,
+ version, wireguard, DaemonEvent,
};
use parking_lot::{Mutex, RwLock};
use std::{
@@ -28,7 +28,7 @@ use std::{
};
use talpid_core::mpsc::IntoSender;
use talpid_ipc;
-use talpid_types::{net::wireguard, ErrorExt};
+use talpid_types::ErrorExt;
use uuid;
/// FIXME(linus): This is here just because the futures crate has deprecated it and jsonrpc_core
@@ -133,7 +133,7 @@ build_rpc_trait! {
/// Generates new wireguard key for current account
#[rpc(meta, name = "generate_wireguard_key")]
- fn generate_wireguard_key(&self, Self::Metadata) -> BoxFuture<mullvad_types::wireguard::KeygenEvent, Error>;
+ fn generate_wireguard_key(&self, Self::Metadata) -> BoxFuture<wireguard::KeygenEvent, Error>;
/// Retrieve a public key for current account if the account has one.
#[rpc(meta, name = "get_wireguard_key")]
@@ -217,7 +217,7 @@ pub enum ManagementCommand {
/// Get the daemon settings
GetSettings(OneshotSender<Settings>),
/// Generate new wireguard key
- GenerateWireguardKey(OneshotSender<mullvad_types::wireguard::KeygenEvent>),
+ GenerateWireguardKey(OneshotSender<wireguard::KeygenEvent>),
/// Return a public key of the currently set wireguard private key, if there is one
GetWireguardKey(OneshotSender<Option<wireguard::PublicKey>>),
/// Verify if the currently set wireguard key is valid.
diff --git a/mullvad-daemon/src/wireguard.rs b/mullvad-daemon/src/wireguard.rs
index 0b8157f712..623d2538fd 100644
--- a/mullvad-daemon/src/wireguard.rs
+++ b/mullvad-daemon/src/wireguard.rs
@@ -1,9 +1,13 @@
use crate::InternalDaemonEvent;
+use chrono::offset::Utc;
use futures::{future::Executor, sync::oneshot, Async, Future, Poll};
use jsonrpc_client_core::Error as JsonRpcError;
-use mullvad_types::{account::AccountToken, wireguard::WireguardData};
+use mullvad_types::account::AccountToken;
+pub use mullvad_types::wireguard::*;
use std::{sync::mpsc, time::Duration};
-pub use talpid_types::net::wireguard::*;
+pub use talpid_types::net::wireguard::{
+ ConnectionConfig, PrivateKey, TunnelConfig, TunnelParameters,
+};
use talpid_types::ErrorExt;
use tokio_core::reactor::Remote;
use tokio_retry::{
@@ -60,17 +64,33 @@ impl KeyManager {
pub fn generate_key_sync(&mut self, account: AccountToken) -> Result<WireguardData> {
self.reset();
let private_key = PrivateKey::new_from_random().map_err(Error::GenerationError)?;
+
+ self.run_future_sync(self.push_future_generator(account, private_key)())
+ .map_err(Self::map_rpc_error)
+ }
+
+ pub fn run_future_sync<T: Send + 'static, E: Send + 'static>(
+ &mut self,
+ fut: impl Future<Item = T, Error = E> + Send + 'static,
+ ) -> std::result::Result<T, E> {
+ self.reset();
let (tx, rx) = oneshot::channel();
- let fut = self.push_future_generator(account, private_key)().then(|result| {
+
+ let _ = self.tokio_remote.execute(fut.then(|result| {
let _ = tx.send(result);
Ok(())
- });
- self.tokio_remote
- .execute(fut)
- .map_err(|_e| Error::ExectuionError)?;
+ }));
+ rx.wait().unwrap()
+ }
- rx.wait()
- .map_err(|_| Error::ExectuionError)?
+ pub fn replace_key(
+ &mut self,
+ account: AccountToken,
+ old_key: PublicKey,
+ ) -> Result<WireguardData> {
+ self.reset();
+ let new_key = PrivateKey::new_from_random().map_err(Error::GenerationError)?;
+ self.run_future_sync(self.replace_key_rpc(account, old_key, new_key))
.map_err(Self::map_rpc_error)
}
@@ -165,12 +185,29 @@ impl KeyManager {
move |addresses| WireguardData {
private_key: key,
addresses,
+ created: Utc::now(),
},
))
};
Box::new(push_future)
}
+ fn replace_key_rpc(
+ &self,
+ account: AccountToken,
+ old_key: PublicKey,
+ new_key: PrivateKey,
+ ) -> impl Future<Item = WireguardData, Error = JsonRpcError> + Send {
+ let mut rpc = mullvad_rpc::WireguardKeyProxy::new(self.http_handle.clone());
+ let new_public_key = new_key.public_key();
+ rpc.replace_wg_key(account.clone(), old_key.key, new_public_key)
+ .map(move |addresses| WireguardData {
+ private_key: new_key,
+ addresses,
+ created: Utc::now(),
+ })
+ }
+
fn map_rpc_error(err: jsonrpc_client_core::Error) -> Error {
match err.kind() {
// TODO: Consider handling the invalid account case too.
diff --git a/mullvad-jni/src/daemon_interface.rs b/mullvad-jni/src/daemon_interface.rs
index f80c645bb6..69d16d4034 100644
--- a/mullvad-jni/src/daemon_interface.rs
+++ b/mullvad-jni/src/daemon_interface.rs
@@ -8,10 +8,9 @@ use mullvad_types::{
settings::Settings,
states::{TargetState, TunnelState},
version::AppVersionInfo,
- wireguard::KeygenEvent,
+ wireguard::{self, KeygenEvent},
};
use parking_lot::Mutex;
-use talpid_types::net::wireguard;
#[derive(Debug, err_derive::Error)]
pub enum Error {
diff --git a/mullvad-jni/src/into_java.rs b/mullvad-jni/src/into_java.rs
index b927014178..28a45c3630 100644
--- a/mullvad-jni/src/into_java.rs
+++ b/mullvad-jni/src/into_java.rs
@@ -546,7 +546,7 @@ impl<'env> IntoJava<'env> for KeygenEvent {
match self {
KeygenEvent::NewKey(public_key) => {
let class = get_class("net/mullvad/mullvadvpn/model/KeygenEvent$NewKey");
- let java_public_key = env.auto_local(public_key.into_java(env));
+ let java_public_key = env.auto_local(public_key.key.into_java(env));
let parameters = [
JValue::Object(java_public_key.as_obj()),
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index c8d267bb8b..91da16f484 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -428,7 +428,7 @@ pub extern "system" fn Java_net_mullvad_mullvadvpn_MullvadDaemon_getWireguardKey
_: JObject<'this>,
) -> JObject<'env> {
match DAEMON_INTERFACE.get_wireguard_key() {
- Ok(public_key) => public_key.into_java(&env),
+ Ok(key) => key.map(|k| k.key).into_java(&env),
Err(error) => {
log::error!(
"{}",
diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs
index 6261ef9c10..a68dfea0fa 100644
--- a/mullvad-rpc/src/lib.rs
+++ b/mullvad-rpc/src/lib.rs
@@ -131,6 +131,12 @@ jsonrpc_client!(pub struct WireguardKeyProxy {
account_token: AccountToken,
public_key: wireguard::PublicKey
) -> RpcRequest<mullvad_types::wireguard::AssociatedAddresses>;
+ pub fn replace_wg_key(
+ &mut self,
+ account_token: AccountToken,
+ old_key: wireguard::PublicKey,
+ new_key: wireguard::PublicKey
+ ) -> RpcRequest<mullvad_types::wireguard::AssociatedAddresses>;
pub fn check_wg_key(
&mut self,
account_token: AccountToken,
diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs
index 635bb7d7da..8675a6e845 100644
--- a/mullvad-types/src/wireguard.rs
+++ b/mullvad-types/src/wireguard.rs
@@ -1,3 +1,4 @@
+use chrono::{offset::Utc, DateTime};
use serde::{Deserialize, Serialize};
use std::fmt;
use talpid_types::net::wireguard;
@@ -7,6 +8,25 @@ use talpid_types::net::wireguard;
pub struct WireguardData {
pub private_key: wireguard::PrivateKey,
pub addresses: AssociatedAddresses,
+ #[serde(default = "Utc::now")]
+ pub created: DateTime<Utc>,
+}
+
+impl WireguardData {
+ /// Create a public key
+ pub fn get_public_key(&self) -> PublicKey {
+ PublicKey {
+ key: self.private_key.public_key(),
+ created: self.created,
+ }
+ }
+}
+
+/// Represents a published public key
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct PublicKey {
+ pub key: wireguard::PublicKey,
+ pub created: DateTime<Utc>,
}
/// Contains a pair of local link addresses that are paired with a specific wireguard
@@ -21,7 +41,7 @@ pub struct AssociatedAddresses {
#[derive(Clone, Debug, Deserialize, Serialize)]
/// Event that is emitted when the daemon has finished generating a key.
pub enum KeygenEvent {
- NewKey(wireguard::PublicKey),
+ NewKey(PublicKey),
TooManyKeys,
GenerationFailure,
}
@@ -29,7 +49,7 @@ pub enum KeygenEvent {
impl fmt::Display for KeygenEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
- KeygenEvent::NewKey(public_key) => write!(f, "New wireguard key {}", public_key),
+ KeygenEvent::NewKey(new_key) => write!(f, "New wireguard key {}", new_key.key),
KeygenEvent::TooManyKeys => write!(f, "Account has too many keys already"),
KeygenEvent::GenerationFailure => write!(f, "Failed to generate new wireguard key"),
}