Skip to content

Commit af0b9a9

Browse files
[webview_flutter_android][webview_flutter_wkwebview] Adds support to respond to recoverable SSL certificate errors (#9281)
Platform impls of #9150 Part of flutter/flutter#36925 ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 3a87d13 commit af0b9a9

File tree

48 files changed

+6829
-1942
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+6829
-1942
lines changed

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 4.7.0
2+
3+
* Adds support to respond to recoverable SSL certificate errors. See `AndroidNavigationDelegate.setOnSSlAuthError`.
4+
15
## 4.6.0
26

37
* Adds support to set using wide view port. See `AndroidWebViewController.setUseWideViewPort`.

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v25.3.1), do not edit directly.
4+
// Autogenerated from Pigeon (v25.3.2), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
77

@@ -561,6 +561,12 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger:
561561
*/
562562
abstract fun getPigeonApiSslCertificate(): PigeonApiSslCertificate
563563

564+
/**
565+
* An implementation of [PigeonApiCertificate] used to add a new Dart instance of `Certificate` to
566+
* the Dart `InstanceManager`.
567+
*/
568+
abstract fun getPigeonApiCertificate(): PigeonApiCertificate
569+
564570
fun setUp() {
565571
AndroidWebkitLibraryPigeonInstanceManagerApi.setUpMessageHandlers(
566572
binaryMessenger, instanceManager)
@@ -591,6 +597,7 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger:
591597
PigeonApiSslCertificateDName.setUpMessageHandlers(
592598
binaryMessenger, getPigeonApiSslCertificateDName())
593599
PigeonApiSslCertificate.setUpMessageHandlers(binaryMessenger, getPigeonApiSslCertificate())
600+
PigeonApiCertificate.setUpMessageHandlers(binaryMessenger, getPigeonApiCertificate())
594601
}
595602

596603
fun tearDown() {
@@ -615,6 +622,7 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger:
615622
PigeonApiSslError.setUpMessageHandlers(binaryMessenger, null)
616623
PigeonApiSslCertificateDName.setUpMessageHandlers(binaryMessenger, null)
617624
PigeonApiSslCertificate.setUpMessageHandlers(binaryMessenger, null)
625+
PigeonApiCertificate.setUpMessageHandlers(binaryMessenger, null)
618626
}
619627
}
620628

@@ -716,6 +724,8 @@ private class AndroidWebkitLibraryPigeonProxyApiBaseCodec(
716724
registrar.getPigeonApiSslCertificateDName().pigeon_newInstance(value) {}
717725
} else if (value is android.net.http.SslCertificate) {
718726
registrar.getPigeonApiSslCertificate().pigeon_newInstance(value) {}
727+
} else if (value is java.security.cert.Certificate) {
728+
registrar.getPigeonApiCertificate().pigeon_newInstance(value) {}
719729
}
720730

721731
when {
@@ -5584,6 +5594,12 @@ open class PigeonApiX509Certificate(
55845594
}
55855595
}
55865596
}
5597+
5598+
@Suppress("FunctionName")
5599+
/** An implementation of [PigeonApiCertificate] used to access callback methods */
5600+
fun pigeon_getPigeonApiCertificate(): PigeonApiCertificate {
5601+
return pigeonRegistrar.getPigeonApiCertificate()
5602+
}
55875603
}
55885604
/**
55895605
* Represents a request for handling an SSL error.
@@ -6153,3 +6169,80 @@ abstract class PigeonApiSslCertificate(
61536169
}
61546170
}
61556171
}
6172+
/**
6173+
* Abstract class for managing a variety of identity certificates.
6174+
*
6175+
* See https://developer.android.com/reference/java/security/cert/Certificate.
6176+
*/
6177+
@Suppress("UNCHECKED_CAST")
6178+
abstract class PigeonApiCertificate(
6179+
open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar
6180+
) {
6181+
/** The encoded form of this certificate. */
6182+
abstract fun getEncoded(pigeon_instance: java.security.cert.Certificate): ByteArray
6183+
6184+
companion object {
6185+
@Suppress("LocalVariableName")
6186+
fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiCertificate?) {
6187+
val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec()
6188+
run {
6189+
val channel =
6190+
BasicMessageChannel<Any?>(
6191+
binaryMessenger,
6192+
"dev.flutter.pigeon.webview_flutter_android.Certificate.getEncoded",
6193+
codec)
6194+
if (api != null) {
6195+
channel.setMessageHandler { message, reply ->
6196+
val args = message as List<Any?>
6197+
val pigeon_instanceArg = args[0] as java.security.cert.Certificate
6198+
val wrapped: List<Any?> =
6199+
try {
6200+
listOf(api.getEncoded(pigeon_instanceArg))
6201+
} catch (exception: Throwable) {
6202+
AndroidWebkitLibraryPigeonUtils.wrapError(exception)
6203+
}
6204+
reply.reply(wrapped)
6205+
}
6206+
} else {
6207+
channel.setMessageHandler(null)
6208+
}
6209+
}
6210+
}
6211+
}
6212+
6213+
@Suppress("LocalVariableName", "FunctionName")
6214+
/** Creates a Dart instance of Certificate and attaches it to [pigeon_instanceArg]. */
6215+
fun pigeon_newInstance(
6216+
pigeon_instanceArg: java.security.cert.Certificate,
6217+
callback: (Result<Unit>) -> Unit
6218+
) {
6219+
if (pigeonRegistrar.ignoreCallsToDart) {
6220+
callback(
6221+
Result.failure(
6222+
AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", "")))
6223+
} else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) {
6224+
callback(Result.success(Unit))
6225+
} else {
6226+
val pigeon_identifierArg =
6227+
pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg)
6228+
val binaryMessenger = pigeonRegistrar.binaryMessenger
6229+
val codec = pigeonRegistrar.codec
6230+
val channelName = "dev.flutter.pigeon.webview_flutter_android.Certificate.pigeon_newInstance"
6231+
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
6232+
channel.send(listOf(pigeon_identifierArg)) {
6233+
if (it is List<*>) {
6234+
if (it.size > 1) {
6235+
callback(
6236+
Result.failure(
6237+
AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?)))
6238+
} else {
6239+
callback(Result.success(Unit))
6240+
}
6241+
} else {
6242+
callback(
6243+
Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName)))
6244+
}
6245+
}
6246+
}
6247+
}
6248+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import androidx.annotation.NonNull;
8+
import java.security.cert.Certificate;
9+
import java.security.cert.CertificateEncodingException;
10+
11+
/**
12+
* ProxyApi implementation for {@link Certificate}. This class may handle instantiating native
13+
* object instances that are attached to a Dart instance or handle method calls on the associated
14+
* native class or an instance of that class.
15+
*/
16+
class CertificateProxyApi extends PigeonApiCertificate {
17+
CertificateProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) {
18+
super(pigeonRegistrar);
19+
}
20+
21+
@NonNull
22+
@Override
23+
public byte[] getEncoded(@NonNull Certificate pigeon_instance) {
24+
try {
25+
return pigeon_instance.getEncoded();
26+
} catch (CertificateEncodingException exception) {
27+
throw new RuntimeException(exception);
28+
}
29+
}
30+
}

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,12 @@ public PigeonApiAndroidMessage getPigeonApiAndroidMessage() {
232232
return new MessageProxyApi(this);
233233
}
234234

235+
@NonNull
236+
@Override
237+
public PigeonApiCertificate getPigeonApiCertificate() {
238+
return new CertificateProxyApi(this);
239+
}
240+
235241
@NonNull
236242
public Context getContext() {
237243
return context;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import static org.junit.Assert.assertEquals;
8+
import static org.mockito.Mockito.mock;
9+
import static org.mockito.Mockito.when;
10+
11+
import java.security.cert.Certificate;
12+
import java.security.cert.CertificateEncodingException;
13+
import org.junit.Test;
14+
15+
public class CertificateTest {
16+
@Test
17+
public void getEncoded() throws CertificateEncodingException {
18+
final PigeonApiCertificate api = new TestProxyApiRegistrar().getPigeonApiCertificate();
19+
20+
final Certificate instance = mock(Certificate.class);
21+
final byte[] value = new byte[] {(byte) 0xA1};
22+
when(instance.getEncoded()).thenReturn(value);
23+
24+
assertEquals(value, api.getEncoded(instance));
25+
}
26+
}

packages/webview_flutter/webview_flutter_android/example/android/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ tasks.register("clean", Delete) {
3030
gradle.projectsEvaluated {
3131
project(":webview_flutter_android") {
3232
tasks.withType(JavaCompile) {
33-
options.compilerArgs << "-Xlint:all" << "-Werror"
33+
// Ignore classfile due to https://issuetracker.google.com/issues/342067844
34+
options.compilerArgs << "-Xlint:all" << "-Werror" << "-Xlint:-classfile"
3435
}
3536
}
3637
}

packages/webview_flutter/webview_flutter_android/example/lib/main.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ Page resource error:
221221
})
222222
..setOnHttpAuthRequest((HttpAuthRequest request) {
223223
openDialog(request);
224+
})
225+
..setOnSSlAuthError((PlatformSslAuthError error) {
226+
debugPrint('SSL error from ${(error as AndroidSslAuthError).url}');
227+
error.cancel();
224228
}),
225229
)
226230
..addJavaScriptChannel(JavaScriptChannelParams(

packages/webview_flutter/webview_flutter_android/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies:
1717
# The example app is bundled with the plugin so we use a path dependency on
1818
# the parent directory to use the current plugin's version.
1919
path: ../
20-
webview_flutter_platform_interface: ^2.12.0
20+
webview_flutter_platform_interface: ^2.13.0
2121

2222
dev_dependencies:
2323
espresso: ^0.4.0
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:meta/meta.dart';
6+
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
7+
import 'android_webkit.g.dart' as android;
8+
9+
/// Implementation of the [PlatformSslAuthError] with the Android WebView API.
10+
class AndroidSslAuthError extends PlatformSslAuthError {
11+
/// Creates an [AndroidSslAuthError].
12+
AndroidSslAuthError._({
13+
required super.certificate,
14+
required super.description,
15+
required android.SslErrorHandler handler,
16+
required this.url,
17+
}) : _handler = handler;
18+
19+
final android.SslErrorHandler _handler;
20+
21+
/// The URL associated with the error.
22+
final String url;
23+
24+
/// Creates an [AndroidSslAuthError] from the parameters from the native
25+
/// `WebViewClient.onReceivedSslError`.
26+
@internal
27+
static Future<AndroidSslAuthError> fromNativeCallback({
28+
required android.SslError error,
29+
required android.SslErrorHandler handler,
30+
}) async {
31+
final android.SslCertificate certificate = error.certificate;
32+
final android.X509Certificate? x509Certificate =
33+
await certificate.getX509Certificate();
34+
35+
final android.SslErrorType errorType = await error.getPrimaryError();
36+
final String errorDescription = switch (errorType) {
37+
android.SslErrorType.dateInvalid =>
38+
'The date of the certificate is invalid.',
39+
android.SslErrorType.expired => 'The certificate has expired.',
40+
android.SslErrorType.idMismatch => 'Hostname mismatch.',
41+
android.SslErrorType.invalid => 'A generic error occurred.',
42+
android.SslErrorType.notYetValid => 'The certificate is not yet valid.',
43+
android.SslErrorType.untrusted =>
44+
'The certificate authority is not trusted.',
45+
android.SslErrorType.unknown => 'The certificate has an unknown error.',
46+
};
47+
48+
return AndroidSslAuthError._(
49+
certificate: X509Certificate(
50+
data:
51+
x509Certificate != null ? await x509Certificate.getEncoded() : null,
52+
),
53+
handler: handler,
54+
description: errorDescription,
55+
url: error.url,
56+
);
57+
}
58+
59+
@override
60+
Future<void> cancel() => _handler.cancel();
61+
62+
@override
63+
Future<void> proceed() => _handler.proceed();
64+
}

0 commit comments

Comments
 (0)