Skip to content

Commit 1ddd3d8

Browse files
committed
Add settings for configuring a keylog file for Wireshark etc
This makes it possible to log all TLS keys (from Send, and all up & downstream proxy connections) so that tools like Wireshark which collect raw packet data can manually decrypt and inspect within the TLS packets as well.
1 parent a79c266 commit 1ddd3d8

File tree

7 files changed

+117
-15
lines changed

7 files changed

+117
-15
lines changed

src/components/settings/proxy-settings-card.tsx

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { observable, action, computed } from 'mobx';
44
import { observer, inject } from "mobx-react";
55

66
import { styled } from '../../styles';
7+
import { isWindows } from '../../util/ui';
78
import { WarningIcon, Icon } from '../../icons';
89

910
import { isValidPortConfiguration, ProxyStore } from '../../model/proxy-store';
@@ -12,7 +13,8 @@ import {
1213
serverVersion,
1314
versionSatisfies,
1415
INITIAL_HTTP2_RANGE,
15-
TLS_PASSTHROUGH_SUPPORTED
16+
TLS_PASSTHROUGH_SUPPORTED,
17+
KEY_LOG_FILE_SUPPORTED
1618
} from '../../services/service-versions';
1719

1820
import { inputValidation } from '../component-utils';
@@ -23,6 +25,7 @@ import {
2325
} from '../common/card';
2426
import { ContentLabel } from '../common/text-content';
2527
import { Select, TextInput } from '../common/inputs';
28+
import { IconButton } from '../common/icon-button';
2629
import {
2730
SettingsButton,
2831
SettingsExplanation,
@@ -85,8 +88,29 @@ const Http2Select = styled(Select)`
8588
padding: 3px;
8689
`;
8790

91+
const TlsKeyLogContainer = styled.div`
92+
margin: 10px 0;
93+
display: flex;
94+
flex-direction: column;
95+
position: relative;
96+
`;
97+
98+
const InputClearButton = styled(IconButton)`
99+
position: absolute;
100+
top: 1px;
101+
right: 2px;
102+
`;
103+
88104
const hostnameValidation = inputValidation(isValidHostname, "Should be a valid hostname");
89105

106+
const isAbsoluteWindowsPath = (path: string) => /^([a-zA-Z]:[\\\/]|[\\\/])((?:[^<>:"\/\\|?*]+)[\\\/]?)*$/.test(path);
107+
const isAbsolutePosixPath = (path: string) => /^\/(?:[^/]+\/?)*$/.test(path);
108+
109+
const pathValidation = inputValidation(
110+
isWindows ? isAbsoluteWindowsPath : isAbsolutePosixPath,
111+
"Should be a valid absolute file path"
112+
);
113+
90114
@inject('proxyStore')
91115
@observer
92116
export class ProxySettingsCard extends React.Component<
@@ -160,6 +184,26 @@ export class ProxySettingsCard extends React.Component<
160184
tlsPassthroughConfig.splice(hostnameIndex, 1);
161185
}
162186

187+
@observable
188+
tlsKeyFileInput: string = this.props.proxyStore!.keyLogFilePath || '';
189+
190+
@action.bound
191+
setTlsKeyFilePath({ target }: React.ChangeEvent<HTMLInputElement>) {
192+
this.tlsKeyFileInput = target.value;
193+
194+
if (!this.tlsKeyFileInput.trim()) {
195+
this.props.proxyStore!.keyLogFilePath = undefined;
196+
} else if (pathValidation(target)) {
197+
this.props.proxyStore!.keyLogFilePath = this.tlsKeyFileInput.trim();
198+
}
199+
}
200+
201+
@action.bound
202+
clearTlsKeyFilePath() {
203+
this.tlsKeyFileInput = '';
204+
this.props.proxyStore!.keyLogFilePath = undefined;
205+
}
206+
163207
render() {
164208
const { proxyStore, ...cardProps } = this.props;
165209
const {
@@ -169,7 +213,10 @@ export class ProxySettingsCard extends React.Component<
169213
http2CurrentlyEnabled,
170214

171215
tlsPassthroughConfig,
172-
currentTlsPassthroughConfig
216+
currentTlsPassthroughConfig,
217+
218+
keyLogFilePath,
219+
currentKeyLogFilePath
173220
} = proxyStore!;
174221

175222
return <CollapsibleCard {...cardProps}>
@@ -184,7 +231,8 @@ export class ProxySettingsCard extends React.Component<
184231
visible={
185232
(this.isCurrentPortConfigValid && !this.isCurrentPortInRange) ||
186233
http2Enabled !== http2CurrentlyEnabled ||
187-
!_.isEqual(tlsPassthroughConfig, currentTlsPassthroughConfig)
234+
!_.isEqual(tlsPassthroughConfig, currentTlsPassthroughConfig) ||
235+
keyLogFilePath !== currentKeyLogFilePath
188236
}
189237
/>
190238

@@ -234,7 +282,7 @@ export class ProxySettingsCard extends React.Component<
234282
versionSatisfies(serverVersion.value, TLS_PASSTHROUGH_SUPPORTED) && <>
235283
<SettingsSubheading>
236284
TLS Passthrough { !_.isEqual(tlsPassthroughConfig, currentTlsPassthroughConfig) &&
237-
<UnsavedIcon />
285+
<UnsavedIcon title="Restart app to apply changes" />
238286
}
239287
</SettingsSubheading>
240288

@@ -259,7 +307,7 @@ export class ProxySettingsCard extends React.Component<
259307
versionSatisfies(serverVersion.value, INITIAL_HTTP2_RANGE) && <>
260308
<SettingsSubheading>
261309
HTTP/2 Support { http2Enabled !== http2CurrentlyEnabled &&
262-
<UnsavedIcon />
310+
<UnsavedIcon title="Restart app to apply changes" />
263311
}
264312
</SettingsSubheading>
265313

@@ -278,6 +326,41 @@ export class ProxySettingsCard extends React.Component<
278326
</Http2Select>
279327
</>
280328
}
329+
330+
{
331+
versionSatisfies(serverVersion.value, KEY_LOG_FILE_SUPPORTED) && <>
332+
<SettingsSubheading>
333+
TLS Key Log File { keyLogFilePath !== currentKeyLogFilePath &&
334+
<UnsavedIcon title="Restart app to apply changes" />
335+
}
336+
</SettingsSubheading>
337+
338+
<TlsKeyLogContainer>
339+
<TextInput
340+
placeholder={
341+
navigator.platform.startsWith('Win')
342+
? 'C:\\tls-keys.log'
343+
: '/tmp/tls-keys.log'
344+
}
345+
value={this.tlsKeyFileInput}
346+
onChange={this.setTlsKeyFilePath}
347+
/>
348+
{ !!keyLogFilePath && <>
349+
<InputClearButton
350+
title="Unset TLS key file"
351+
icon={['fas', 'times']}
352+
onClick={this.clearTlsKeyFilePath}
353+
/>
354+
</> }
355+
</TlsKeyLogContainer>
356+
357+
<SettingsExplanation>
358+
If set, TLS keys for all client & server traffic will be logged to this file,
359+
allowing inspection of raw TLS packet contents & details in low-level packet
360+
inspection tools like Wireshark.
361+
</SettingsExplanation>
362+
</>
363+
}
281364
</CollapsibleCard>
282365
}
283366

src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const rulesStore = new RulesStore(accountStore, proxyStore,
8282
}
8383
);
8484
const eventsStore = new EventsStore(proxyStore, apiStore, rulesStore);
85-
const sendStore = new SendStore(accountStore, eventsStore, rulesStore);
85+
const sendStore = new SendStore(accountStore, eventsStore, rulesStore, proxyStore);
8686

8787
const stores = {
8888
accountStore,

src/model/proxy-store.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,11 @@ export class ProxyStore {
176176
adminServerUrl: 'http://127.0.0.1:45456'
177177
});
178178

179+
// These are persisted initially, so we know if the user updates them that we
180+
// need to restart the proxy:
179181
this._http2CurrentlyEnabled = this.http2Enabled;
180182
this._currentTlsPassthroughConfig = _.cloneDeep(this.tlsPassthroughConfig);
183+
this._currentKeyLogFilePath = this.keyLogFilePath;
181184

182185
this.monitorRemoteClientConnection(this.adminClient);
183186

@@ -189,7 +192,8 @@ export class ProxyStore {
189192
// User configurable settings:
190193
http2: this._http2CurrentlyEnabled,
191194
https: {
192-
tlsPassthrough: this._currentTlsPassthroughConfig
195+
tlsPassthrough: this._currentTlsPassthroughConfig,
196+
keyLogFile: this._currentKeyLogFilePath
193197
} as MockttpHttpsOptions, // Cert/Key options are set by the server
194198
socks: true,
195199
passthrough: this.accountStore.featureFlags.includes('raw-tunnels')
@@ -296,6 +300,13 @@ export class ProxyStore {
296300
return this._currentTlsPassthroughConfig;
297301
}
298302

303+
@persist @observable
304+
keyLogFilePath: string | undefined = undefined;
305+
private _currentKeyLogFilePath: string | undefined = this.keyLogFilePath;
306+
get currentKeyLogFilePath() {
307+
return this._currentKeyLogFilePath;
308+
}
309+
299310
setRequestRules = (...rules: RequestRuleData[]) => {
300311
const { adminStream } = this.adminClient;
301312

src/model/send/send-request-model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export interface RequestOptions {
150150
clientCertificate?: { pfx: Buffer, passphrase?: string };
151151
proxyConfig?: ClientProxyConfig;
152152
lookupOptions?: { servers?: string[] };
153+
keyLogFile?: string;
153154
}
154155

155156
export const RULE_PARAM_REF_KEY = '__rule_param_reference__';

src/model/send/send-store.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { trackEvent } from '../../metrics';
2222
import { EventsStore } from '../events/events-store';
2323
import { RulesStore } from '../rules/rules-store';
2424
import { AccountStore } from '../account/account-store';
25+
import { ProxyStore } from '../proxy-store';
2526
import * as ServerApi from '../../services/server-api';
2627

2728
import { HttpExchange } from '../http/http-exchange';
@@ -32,22 +33,25 @@ import {
3233
RequestInput,
3334
SendRequest,
3435
RULE_PARAM_REF_KEY,
35-
sendRequestSchema
36+
sendRequestSchema,
37+
RequestOptions
3638
} from './send-request-model';
3739

3840
export class SendStore {
3941

4042
constructor(
4143
private accountStore: AccountStore,
4244
private eventStore: EventsStore,
43-
private rulesStore: RulesStore
45+
private rulesStore: RulesStore,
46+
private proxyStore: ProxyStore
4447
) {}
4548

4649
readonly initialized = lazyObservablePromise(async () => {
4750
await Promise.all([
4851
this.accountStore.initialized,
4952
this.eventStore.initialized,
50-
this.rulesStore.initialized
53+
this.rulesStore.initialized,
54+
this.proxyStore.initialized
5155
]);
5256

5357
if (this.accountStore.mightBePaidUser) {
@@ -166,13 +170,14 @@ export class SendStore {
166170
({ cert: cert.rawPEM })
167171
);
168172

169-
const requestOptions = {
173+
const requestOptions: RequestOptions = {
170174
ignoreHostHttpsErrors: passthroughOptions.ignoreHostHttpsErrors,
171-
additionalCACerts: additionalCACerts,
175+
additionalTrustedCAs: additionalCACerts,
172176
trustAdditionalCAs: additionalCACerts, // Deprecated alias, here for backward compat
173177
clientCertificate,
174178
proxyConfig: getProxyConfig(this.rulesStore.proxyConfig),
175-
lookupOptions: passthroughOptions.lookupOptions
179+
lookupOptions: passthroughOptions.lookupOptions,
180+
keyLogFile: this.proxyStore.keyLogFilePath
176181
};
177182

178183
const encodedBody = await requestInput.rawBody.encodingBestEffortPromise;

src/services/service-versions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,5 @@ export const CONNECTION_RESET_SUPPORTED = '^1.12.0';
8585
export const SERVER_REST_API_SUPPORTED = '^1.13.0';
8686
export const SERVER_SEND_API_SUPPORTED = '^1.13.0';
8787
export const ADVANCED_PATCH_TRANSFORMS = '^1.18.0';
88-
export const WILDCARD_CLIENT_CERTS = '^1.22.0';
88+
export const WILDCARD_CLIENT_CERTS = '^1.22.0';
89+
export const KEY_LOG_FILE_SUPPORTED = '^1.23.0';

src/util/ui.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ window.addEventListener('resize', action(() => {
1919
windowSize.width = window.innerWidth;
2020
}));
2121

22-
const isMac = navigator.platform.startsWith('Mac');
22+
export const isWindows = navigator.platform.startsWith('Win');
23+
export const isMac = navigator.platform.startsWith('Mac');
2324

2425
export const Ctrl = isMac
2526
? '⌘'

0 commit comments

Comments
 (0)