Skip to content

Commit 428bff1

Browse files
authored
#60704: Pass cert for TLS localhost connection (#106635)
*Pass locally generated certificate via command line* *Fixes: #60704* Added ARGS: - web-tls-cert-path - web-tls-cert-key-path Passing the path of local certificate and the key to cert will allow flutter tool to create a secure debugging session on chrome **Pre-launch Checklist** � I read the [Contributor Guide](https://github.com/flutter/flutter/wiki/Tree-hygiene#overview) and followed the process outlined there for submitting PRs. � I read the [Tree Hygiene](https://github.com/flutter/flutter/wiki/Tree-hygiene) wiki page, which explains my responsibilities. � I read and followed the [Flutter Style Guide](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo), including [Features we expect every widget to implement](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement). � I signed the [CLA](https://cla.developers.google.com/). � I listed at least one issue that this PR fixes in the description above. � I updated/added relevant documentation (doc comments with ///). � I added new tests to check the change I am making. � All existing and new tests are passing.
1 parent 9be8f4f commit 428bff1

File tree

9 files changed

+155
-6
lines changed

9 files changed

+155
-6
lines changed

packages/flutter_tools/lib/src/base/io.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export 'dart:io'
9393
// ProcessSignal NO! Use [ProcessSignal] below.
9494
ProcessStartMode,
9595
// RandomAccessFile NO! Use `file_system.dart`
96+
SecurityContext,
9697
ServerSocket,
9798
SignalException,
9899
Socket,

packages/flutter_tools/lib/src/commands/run.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
248248
dartEntrypointArgs: stringsArg('dart-entrypoint-args'),
249249
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
250250
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
251+
tlsCertPath: featureFlags.isWebEnabled ? stringArg('web-tls-cert-path') : null,
252+
tlsCertKeyPath: featureFlags.isWebEnabled ? stringArg('web-tls-cert-key-path') : null,
251253
webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse',
252254
webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse',
253255
webUseSseForInjectedClient: featureFlags.isWebEnabled && stringArg('web-server-debug-injected-client-protocol') == 'sse',
@@ -293,6 +295,8 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
293295
verboseSystemLogs: boolArg('verbose-system-logs'),
294296
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
295297
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
298+
tlsCertPath: featureFlags.isWebEnabled ? stringArg('web-tls-cert-path') : null,
299+
tlsCertKeyPath: featureFlags.isWebEnabled ? stringArg('web-tls-cert-key-path') : null,
296300
webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse',
297301
webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse',
298302
webUseSseForInjectedClient: featureFlags.isWebEnabled && stringArg('web-server-debug-injected-client-protocol') == 'sse',

packages/flutter_tools/lib/src/device.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,8 @@ class DebuggingOptions {
951951
this.devToolsServerAddress,
952952
this.hostname,
953953
this.port,
954+
this.tlsCertPath,
955+
this.tlsCertKeyPath,
954956
this.webEnableExposeUrl,
955957
this.webUseSseForDebugProxy = true,
956958
this.webUseSseForDebugBackend = true,
@@ -979,6 +981,8 @@ class DebuggingOptions {
979981
this.dartEntrypointArgs = const <String>[],
980982
this.port,
981983
this.hostname,
984+
this.tlsCertPath,
985+
this.tlsCertKeyPath,
982986
this.webEnableExposeUrl,
983987
this.webUseSseForDebugProxy = true,
984988
this.webUseSseForDebugBackend = true,
@@ -1055,6 +1059,8 @@ class DebuggingOptions {
10551059
required this.devToolsServerAddress,
10561060
required this.port,
10571061
required this.hostname,
1062+
required this.tlsCertPath,
1063+
required this.tlsCertKeyPath,
10581064
required this.webEnableExposeUrl,
10591065
required this.webUseSseForDebugProxy,
10601066
required this.webUseSseForDebugBackend,
@@ -1108,6 +1114,8 @@ class DebuggingOptions {
11081114
final Uri? devToolsServerAddress;
11091115
final String? port;
11101116
final String? hostname;
1117+
final String? tlsCertPath;
1118+
final String? tlsCertKeyPath;
11111119
final bool? webEnableExposeUrl;
11121120
final bool webUseSseForDebugProxy;
11131121
final bool webUseSseForDebugBackend;
@@ -1243,6 +1251,8 @@ class DebuggingOptions {
12431251
'devToolsServerAddress': devToolsServerAddress.toString(),
12441252
'port': port,
12451253
'hostname': hostname,
1254+
'tlsCertPath': tlsCertPath,
1255+
'tlsCertKeyPath': tlsCertKeyPath,
12461256
'webEnableExposeUrl': webEnableExposeUrl,
12471257
'webUseSseForDebugProxy': webUseSseForDebugProxy,
12481258
'webUseSseForDebugBackend': webUseSseForDebugBackend,
@@ -1296,6 +1306,8 @@ class DebuggingOptions {
12961306
devToolsServerAddress: json['devToolsServerAddress'] != null ? Uri.parse(json['devToolsServerAddress']! as String) : null,
12971307
port: json['port'] as String?,
12981308
hostname: json['hostname'] as String?,
1309+
tlsCertPath: json['tlsCertPath'] as String?,
1310+
tlsCertKeyPath: json['tlsCertKeyPath'] as String?,
12991311
webEnableExposeUrl: json['webEnableExposeUrl'] as bool?,
13001312
webUseSseForDebugProxy: json['webUseSseForDebugProxy']! as bool,
13011313
webUseSseForDebugBackend: json['webUseSseForDebugBackend']! as bool,

packages/flutter_tools/lib/src/isolated/devfs_web.dart

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ class WebAssetServer implements AssetReader {
164164
ChromiumLauncher? chromiumLauncher,
165165
String hostname,
166166
int port,
167+
String? tlsCertPath,
168+
String? tlsCertKeyPath,
167169
UrlTunneller? urlTunneller,
168170
bool useSseForDebugProxy,
169171
bool useSseForDebugBackend,
@@ -188,7 +190,14 @@ class WebAssetServer implements AssetReader {
188190
const int kMaxRetries = 4;
189191
for (int i = 0; i <= kMaxRetries; i++) {
190192
try {
191-
httpServer = await HttpServer.bind(address, port);
193+
if (tlsCertPath != null && tlsCertKeyPath != null) {
194+
final SecurityContext serverContext = SecurityContext()
195+
..useCertificateChain(tlsCertPath)
196+
..usePrivateKey(tlsCertKeyPath);
197+
httpServer = await HttpServer.bindSecure(address, port, serverContext);
198+
} else {
199+
httpServer = await HttpServer.bind(address, port);
200+
}
192201
break;
193202
} on SocketException catch (e, s) {
194203
if (i >= kMaxRetries) {
@@ -635,6 +644,8 @@ class WebDevFS implements DevFS {
635644
WebDevFS({
636645
required this.hostname,
637646
required int port,
647+
required this.tlsCertPath,
648+
required this.tlsCertKeyPath,
638649
required this.packagesFilePath,
639650
required this.urlTunneller,
640651
required this.useSseForDebugProxy,
@@ -671,6 +682,8 @@ class WebDevFS implements DevFS {
671682
final bool nativeNullAssertions;
672683
final int _port;
673684
final NullSafetyMode nullSafetyMode;
685+
final String? tlsCertPath;
686+
final String? tlsCertKeyPath;
674687

675688
late WebAssetServer webAssetServer;
676689

@@ -757,6 +770,8 @@ class WebDevFS implements DevFS {
757770
chromiumLauncher,
758771
hostname,
759772
_port,
773+
tlsCertPath,
774+
tlsCertKeyPath,
760775
urlTunneller,
761776
useSseForDebugProxy,
762777
useSseForDebugBackend,
@@ -777,10 +792,13 @@ class WebDevFS implements DevFS {
777792
} else if (buildInfo.dartDefines.contains('FLUTTER_WEB_USE_SKIA=true')) {
778793
webAssetServer.webRenderer = WebRendererMode.canvaskit;
779794
}
795+
String url = '$hostname:$selectedPort';
780796
if (hostname == 'any') {
781-
_baseUri = Uri.http('localhost:$selectedPort', webAssetServer.basePath);
782-
} else {
783-
_baseUri = Uri.http('$hostname:$selectedPort', webAssetServer.basePath);
797+
url ='localhost:$selectedPort';
798+
}
799+
_baseUri = Uri.http(url, webAssetServer.basePath);
800+
if (tlsCertPath != null && tlsCertKeyPath!= null) {
801+
_baseUri = Uri.https(url, webAssetServer.basePath);
784802
}
785803
return _baseUri!;
786804
}

packages/flutter_tools/lib/src/isolated/resident_web_runner.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
293293
device!.devFS = WebDevFS(
294294
hostname: debuggingOptions.hostname ?? 'localhost',
295295
port: await getPort(),
296+
tlsCertPath: debuggingOptions.tlsCertPath,
297+
tlsCertKeyPath: debuggingOptions.tlsCertKeyPath,
296298
packagesFilePath: packagesFilePath,
297299
urlTunneller: _urlTunneller,
298300
useSseForDebugProxy: debuggingOptions.webUseSseForDebugProxy,
@@ -309,7 +311,10 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
309311
nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode,
310312
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
311313
);
312-
final Uri url = await device!.devFS!.create();
314+
Uri url = await device!.devFS!.create();
315+
if (debuggingOptions.tlsCertKeyPath != null && debuggingOptions.tlsCertPath != null) {
316+
url = url.replace(scheme: 'https');
317+
}
313318
if (debuggingOptions.buildInfo.isDebug) {
314319
await runSourceGenerators();
315320
final UpdateFSReport report = await _updateDevFS(fullRestart: true);
@@ -343,7 +348,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
343348
mainPath: target,
344349
debuggingOptions: debuggingOptions,
345350
platformArgs: <String, Object>{
346-
'uri': url.toString(),
351+
'uri': url.toString(),
347352
},
348353
);
349354
return attach(

packages/flutter_tools/lib/src/runner/flutter_command.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,16 @@ abstract class FlutterCommand extends Command<void> {
261261
'will select a random open port on the host.',
262262
hide: !verboseHelp,
263263
);
264+
argParser.addOption(
265+
'web-tls-cert-path',
266+
help: 'The certificate that host will use to serve using TLS connection. '
267+
'If not provided, the tool will use default http scheme.',
268+
);
269+
argParser.addOption(
270+
'web-tls-cert-key-path',
271+
help: 'The certificate key that host will use to authenticate cert. '
272+
'If not provided, the tool will use default http scheme.',
273+
);
264274
argParser.addOption('web-server-debug-protocol',
265275
allowed: <String>['sse', 'ws'],
266276
defaultsTo: 'ws',
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICjTCCAfYCCQDR1evIEbvoVjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
3+
VVMxEjAQBgNVBAgMCVNvbWV3aGVyZTERMA8GA1UEBwwIU29tZUNpdHkxDTALBgNV
4+
BAoMBENvcnAxETAPBgNVBAsMCFNvZnR3YXJlMRIwEAYDVQQDDAlsb2NhbGhvc3Qx
5+
HjAcBgkqhkiG9w0BCQEWD2FkbWluQGxvY2FsaG9zdDAeFw0xMjA2MDgxOTE0Mzha
6+
Fw0xMjA3MDgxOTE0MzhaMIGKMQswCQYDVQQGEwJVUzESMBAGA1UECAwJU29tZXdo
7+
ZXJlMREwDwYDVQQHDAhTb21lQ2l0eTENMAsGA1UECgwEQ29ycDERMA8GA1UECwwI
8+
U29mdHdhcmUxEjAQBgNVBAMMCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPYWRt
9+
aW5AbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/Vj4UCdQI
10+
N0IMCHDWwDo3QyH9I8sBm/OwIiiJbQ0RpyfWCn4ilzZwu98okwUCu5PwlFQZd67a
11+
DooxhFS2FSw4iRZCUGJlgV7BG1JX9q0xqVy33V6rxFFYQYHw6r7FHPaw2FuRCOoi
12+
uDqE+ua6lbV2YP/eXiRnq5hT5vWfEX5rYwIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
13+
ADQFczkmr+91I3id7HH1voh9YKVqA9nh1yYFCkonsDPXBEJIZEyYa9HaWVkcMMgo
14+
F7bJbWKyaYXW818XPXuOmSBI7dmaMtqITEJWsxdcMVKYCOMtjTLwquPki6xXZxNb
15+
y/zubrV+P4XN0Wi0hoDU/0/RNQOuAF1w7UOQsUmn1ihj
16+
-----END CERTIFICATE-----
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIICXQIBAAKBgQC/Vj4UCdQIN0IMCHDWwDo3QyH9I8sBm/OwIiiJbQ0RpyfWCn4i
3+
lzZwu98okwUCu5PwlFQZd67aDooxhFS2FSw4iRZCUGJlgV7BG1JX9q0xqVy33V6r
4+
xFFYQYHw6r7FHPaw2FuRCOoiuDqE+ua6lbV2YP/eXiRnq5hT5vWfEX5rYwIDAQAB
5+
AoGAaL5oq4WZ2omNkZLJWvbOp9QLdk2y44WhSOnaMSlOvzw3tZf25y7KcbqXdtnN
6+
I2rWmRxKUcrQILVW97aOvUMn+jOCN6+IY1kiT6Un4H1Ow+rVj3CDvCjBCZhfkExK
7+
osTzpwbiRyHueWOLOB3RZUNXC+5OfTBVoYgQp85INykwUHECQQD10XsOKDtkuCh4
8+
yNf68lXC7TUSPAwAjX+I4xJ1UWMO5DWBpuyuA2GSlYHWZg4ae0xm9zUijb6A5WDW
9+
aVTvi9S5AkEAx0MSU2qD837lXAjkcyBS9WqtoJebC263uUaiQ8WZQhDE+R8aeXj+
10+
e80hY8FOc6WogC2VbQgYO52t82KWLDKq+wJAS+2Xl+jfZ53mimBnLhE6YkpIsUgw
11+
4N7T/OE+q1QnR8s/p7t6sclDkzZw81t0kcNx9v/2vqSPqlqvjardXFyRqQJBAIAW
12+
jWExx0BvAeD3lmKrFKjNum7RBcmDknZ3ATevfaUKQpQhelM7g9rxMdV+HYAZrQc4
13+
RiWgXnN0GK2rYf1nVKECQQCo5UHoukW89+UX/SbamoMeUZJxL3bQAqv71B59C6cy
14+
NQuDZlHOqDajyxaX2y8tJWgk3ciGXlIqByHQFXb2Rhuw
15+
-----END RSA PRIVATE KEY-----

packages/flutter_tools/test/general.shard/web/devfs_web_test.dart

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,8 @@ void main() {
662662
final WebDevFS webDevFS = WebDevFS(
663663
hostname: 'localhost',
664664
port: 0,
665+
tlsCertPath: null,
666+
tlsCertKeyPath: null,
665667
packagesFilePath: '.packages',
666668
urlTunneller: null, // ignore: avoid_redundant_argument_values
667669
useSseForDebugProxy: true,
@@ -777,6 +779,8 @@ void main() {
777779
final WebDevFS webDevFS = WebDevFS(
778780
hostname: 'localhost',
779781
port: 0,
782+
tlsCertPath: null,
783+
tlsCertKeyPath: null,
780784
packagesFilePath: '.packages',
781785
urlTunneller: null, // ignore: avoid_redundant_argument_values
782786
useSseForDebugProxy: true,
@@ -888,6 +892,8 @@ void main() {
888892
// if this is any other value, we will do a real ip lookup
889893
hostname: 'any',
890894
port: 0,
895+
tlsCertPath: null,
896+
tlsCertKeyPath: null,
891897
packagesFilePath: '.packages',
892898
urlTunneller: null,
893899
useSseForDebugProxy: true,
@@ -951,6 +957,8 @@ void main() {
951957
final WebDevFS webDevFS = WebDevFS(
952958
hostname: 'any',
953959
port: 0,
960+
tlsCertPath: null,
961+
tlsCertKeyPath: null,
954962
packagesFilePath: '.packages',
955963
urlTunneller: null, // ignore: avoid_redundant_argument_values
956964
useSseForDebugProxy: true,
@@ -987,6 +995,8 @@ void main() {
987995
final WebDevFS webDevFS = WebDevFS(
988996
hostname: 'localhost',
989997
port: 0,
998+
tlsCertPath: null,
999+
tlsCertKeyPath: null,
9901000
packagesFilePath: '.packages',
9911001
urlTunneller: null, // ignore: avoid_redundant_argument_values
9921002
useSseForDebugProxy: true,
@@ -1031,6 +1041,8 @@ void main() {
10311041
final WebDevFS webDevFS = WebDevFS(
10321042
hostname: 'localhost',
10331043
port: 0,
1044+
tlsCertPath: null,
1045+
tlsCertKeyPath: null,
10341046
packagesFilePath: '.packages',
10351047
urlTunneller: null, // ignore: avoid_redundant_argument_values
10361048
useSseForDebugProxy: true,
@@ -1065,12 +1077,64 @@ void main() {
10651077
await webDevFS.destroy();
10661078
}));
10671079

1080+
test('Can start web server with tls connection', () => testbed.run(() async {
1081+
final String dataPath = globals.fs.path.join(
1082+
getFlutterRoot(),
1083+
'packages',
1084+
'flutter_tools',
1085+
'test',
1086+
'data',
1087+
'asset_test',
1088+
);
1089+
1090+
final String dummyCertPath =
1091+
globals.fs.path.join(dataPath, 'tls_cert', 'dummy-cert.pem');
1092+
final String dummyCertKeyPath =
1093+
globals.fs.path.join(dataPath, 'tls_cert', 'dummy-key.pem');
1094+
1095+
final WebDevFS webDevFS = WebDevFS(
1096+
hostname: 'localhost',
1097+
port: 0,
1098+
tlsCertPath: dummyCertPath,
1099+
tlsCertKeyPath: dummyCertKeyPath,
1100+
packagesFilePath: '.packages',
1101+
urlTunneller: null, // ignore: avoid_redundant_argument_values
1102+
useSseForDebugProxy: true,
1103+
useSseForDebugBackend: true,
1104+
useSseForInjectedClient: true,
1105+
nullAssertions: true,
1106+
nativeNullAssertions: true,
1107+
buildInfo: BuildInfo.debug,
1108+
enableDwds: false,
1109+
enableDds: false,
1110+
entrypoint: Uri.base,
1111+
testMode: true,
1112+
expressionCompiler: null, // ignore: avoid_redundant_argument_values
1113+
extraHeaders: const <String, String>{},
1114+
chromiumLauncher: null, // ignore: avoid_redundant_argument_values
1115+
nullSafetyMode: NullSafetyMode.unsound,
1116+
);
1117+
webDevFS.requireJS.createSync(recursive: true);
1118+
webDevFS.stackTraceMapper.createSync(recursive: true);
1119+
1120+
final Uri uri = await webDevFS.create();
1121+
1122+
// Ensure the connection established is secure
1123+
expect(uri.scheme, 'https');
1124+
1125+
await webDevFS.destroy();
1126+
}, overrides: <Type, Generator>{
1127+
Artifacts: () => Artifacts.test(),
1128+
}));
1129+
10681130
test('allows frame embedding', () async {
10691131
final WebAssetServer webAssetServer = await WebAssetServer.start(
10701132
null,
10711133
'localhost',
10721134
0,
10731135
null,
1136+
null,
1137+
null,
10741138
true,
10751139
true,
10761140
true,
@@ -1099,6 +1163,8 @@ void main() {
10991163
'localhost',
11001164
0,
11011165
null,
1166+
null,
1167+
null,
11021168
true,
11031169
true,
11041170
true,
@@ -1174,6 +1240,8 @@ void main() {
11741240
final WebDevFS webDevFS = WebDevFS(
11751241
hostname: 'localhost',
11761242
port: 0,
1243+
tlsCertPath: null,
1244+
tlsCertKeyPath: null,
11771245
packagesFilePath: '.packages',
11781246
urlTunneller: null, // ignore: avoid_redundant_argument_values
11791247
useSseForDebugProxy: true,

0 commit comments

Comments
 (0)