Skip to content

Commit 7bdffbe

Browse files
[webview_flutter_wkwebview] Fixes loadFlutterAsset exception and updates native wrapper for SecTrust and SecCertificate (#9016)
Adds support to the native wrapper to handle `SecTrust` and `SecCertificate`. This is a part of landing #7893 by splitting of the native wrapper implementation. Also fixes flutter/flutter#162938 and adds an integration test for `loadFlutterAsset`. ## 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 f26b681 commit 7bdffbe

26 files changed

+1698
-74
lines changed

packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 3.18.6
2+
3+
* Fixes `PlatformException` when calling `loadFlutterAsset` on macOS.
4+
* Updates native wrapper with methods handling `SecTust` and `SecCertificate`.
5+
16
## 3.18.5
27

38
* Fixes crash when sending undefined message via JavaScript channel.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 XCTest
6+
7+
@testable import webview_flutter_wkwebview
8+
9+
class GetTrustResultResponseProxyAPITests: XCTestCase {
10+
func testResult() {
11+
let registrar = TestProxyApiRegistrar()
12+
let api = registrar.apiDelegate.pigeonApiGetTrustResultResponse(registrar)
13+
14+
let instance = GetTrustResultResponse(result: SecTrustResultType.invalid, resultCode: -1)
15+
let value = try? api.pigeonDelegate.result(pigeonApi: api, pigeonInstance: instance)
16+
17+
XCTAssertEqual(value, DartSecTrustResultType.invalid)
18+
}
19+
20+
func testResultCode() {
21+
let registrar = TestProxyApiRegistrar()
22+
let api = registrar.apiDelegate.pigeonApiGetTrustResultResponse(registrar)
23+
24+
let instance = GetTrustResultResponse(result: SecTrustResultType.invalid, resultCode: -1)
25+
let value = try? api.pigeonDelegate.resultCode(pigeonApi: api, pigeonInstance: instance)
26+
27+
XCTAssertEqual(value, -1)
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 XCTest
6+
7+
@testable import webview_flutter_wkwebview
8+
9+
#if os(iOS)
10+
import Flutter
11+
#elseif os(macOS)
12+
import FlutterMacOS
13+
#else
14+
#error("Unsupported platform.")
15+
#endif
16+
17+
class SecCertificateProxyAPITests: XCTestCase {
18+
func createDummyCertificate() -> SecCertificate {
19+
let url = FlutterAssetManager().urlForAsset("assets/test_cert.der")!
20+
let certificateData = NSData(contentsOf: url)
21+
22+
return SecCertificateCreateWithData(nil, certificateData!)!
23+
}
24+
25+
func testCopyData() {
26+
let registrar = TestProxyApiRegistrar()
27+
let delegate = TestSecCertificateProxyAPIDelegate()
28+
let api = PigeonApiSecCertificate(pigeonRegistrar: registrar, delegate: delegate)
29+
30+
let value = try? api.pigeonDelegate.copyData(
31+
pigeonApi: api, certificate: SecCertificateWrapper(value: createDummyCertificate()))
32+
33+
XCTAssertEqual(value?.data, delegate.data)
34+
}
35+
}
36+
37+
class TestSecCertificateProxyAPIDelegate: SecCertificateProxyAPIDelegate {
38+
let data = Data()
39+
40+
override func secCertificateCopyData(_ certificate: SecCertificate) -> CFData {
41+
return data as CFData
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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 XCTest
6+
7+
@testable import webview_flutter_wkwebview
8+
9+
#if os(iOS)
10+
import Flutter
11+
#elseif os(macOS)
12+
import FlutterMacOS
13+
#else
14+
#error("Unsupported platform.")
15+
#endif
16+
17+
class SecTrustProxyAPITests: XCTestCase {
18+
func createTrust(delegate: TestSecTrustProxyAPIDelegate) -> SecTrustWrapper {
19+
var trust: SecTrust?
20+
SecTrustCreateWithCertificates(
21+
[delegate.createDummyCertificate()] as AnyObject, SecPolicyCreateBasicX509(), &trust)
22+
23+
return SecTrustWrapper(value: trust!)
24+
}
25+
26+
func testEvaluateWithError() {
27+
let registrar = TestProxyApiRegistrar()
28+
let delegate = TestSecTrustProxyAPIDelegate()
29+
let api = PigeonApiSecTrust(pigeonRegistrar: registrar, delegate: delegate)
30+
31+
let expect = expectation(description: "Wait for setCookie.")
32+
let trust = createTrust(delegate: delegate)
33+
var resultValue: Bool?
34+
35+
api.pigeonDelegate.evaluateWithError(pigeonApi: api, trust: trust) { result in
36+
switch result {
37+
case .success(let value):
38+
resultValue = value
39+
case .failure(_):
40+
break
41+
}
42+
expect.fulfill()
43+
}
44+
45+
wait(for: [expect], timeout: 5.0)
46+
XCTAssertEqual(resultValue, true)
47+
}
48+
49+
func testCopyExceptions() {
50+
let registrar = TestProxyApiRegistrar()
51+
let delegate = TestSecTrustProxyAPIDelegate()
52+
let api = PigeonApiSecTrust(pigeonRegistrar: registrar, delegate: delegate)
53+
54+
let trust = createTrust(delegate: delegate)
55+
let value = try? api.pigeonDelegate.copyExceptions(pigeonApi: api, trust: trust)
56+
57+
XCTAssertEqual(value?.data, Data())
58+
}
59+
60+
func testSetExceptions() {
61+
let registrar = TestProxyApiRegistrar()
62+
let delegate = TestSecTrustProxyAPIDelegate()
63+
let api = PigeonApiSecTrust(pigeonRegistrar: registrar, delegate: delegate)
64+
65+
let trust = createTrust(delegate: delegate)
66+
let value = try? api.pigeonDelegate.setExceptions(
67+
pigeonApi: api, trust: trust, exceptions: FlutterStandardTypedData(bytes: Data()))
68+
69+
XCTAssertEqual(value, false)
70+
}
71+
72+
func testGetTrustResult() {
73+
let registrar = TestProxyApiRegistrar()
74+
let delegate = TestSecTrustProxyAPIDelegate()
75+
let api = PigeonApiSecTrust(pigeonRegistrar: registrar, delegate: delegate)
76+
77+
let trust = createTrust(delegate: delegate)
78+
let value = try? api.pigeonDelegate.getTrustResult(pigeonApi: api, trust: trust)
79+
80+
XCTAssertEqual(value?.result, SecTrustResultType.invalid)
81+
XCTAssertEqual(value?.resultCode, -1)
82+
}
83+
84+
func testCopyCertificateChain() {
85+
let registrar = TestProxyApiRegistrar()
86+
let delegate = TestSecTrustProxyAPIDelegate()
87+
let api = PigeonApiSecTrust(pigeonRegistrar: registrar, delegate: delegate)
88+
89+
let trust = createTrust(delegate: delegate)
90+
let value = try? api.pigeonDelegate.copyCertificateChain(pigeonApi: api, trust: trust)
91+
92+
XCTAssertEqual(value?.count, 1)
93+
XCTAssertNotNil(value?.first?.value)
94+
}
95+
}
96+
97+
class TestSecTrustProxyAPIDelegate: SecTrustProxyAPIDelegate {
98+
func createDummyCertificate() -> SecCertificate {
99+
let url = FlutterAssetManager().urlForAsset("assets/test_cert.der")!
100+
let certificateData = NSData(contentsOf: url)
101+
102+
return SecCertificateCreateWithData(nil, certificateData!)!
103+
}
104+
105+
override func secTrustEvaluateWithError(
106+
_ trust: SecTrust, _ error: UnsafeMutablePointer<CFError?>?
107+
) -> Bool {
108+
return true
109+
}
110+
111+
override func secTrustCopyExceptions(_ trust: SecTrust) -> CFData? {
112+
return Data() as CFData
113+
}
114+
115+
override func secTrustSetExceptions(_ trust: SecTrust, _ exceptions: CFData?) -> Bool {
116+
return false
117+
}
118+
119+
override func secTrustGetTrustResult(
120+
_ trust: SecTrust, _ result: UnsafeMutablePointer<SecTrustResultType>
121+
) -> OSStatus {
122+
result.pointee = SecTrustResultType.invalid
123+
return -1
124+
}
125+
126+
override func secTrustCopyCertificateChain(_ trust: SecTrust) -> CFArray? {
127+
if #available(iOS 15.0, *) {
128+
return [createDummyCertificate()] as CFArray
129+
}
130+
131+
return nil
132+
}
133+
}

packages/webview_flutter/webview_flutter_wkwebview/darwin/Tests/TestProxyApiRegistrar.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import XCTest
88

99
class TestProxyApiRegistrar: ProxyAPIRegistrar {
1010
init() {
11-
super.init(binaryMessenger: TestBinaryMessenger(), bundle: TestBundle())
11+
super.init(
12+
binaryMessenger: TestBinaryMessenger(),
13+
assetManager: FlutterAssetManager(bundle: TestBundle()))
1214
}
1315

1416
override func dispatchOnMainThread(

packages/webview_flutter/webview_flutter_wkwebview/darwin/Tests/URLProtectionSpaceProxyAPITests.swift

+30
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,34 @@ class ProtectionSpaceProxyAPITests: XCTestCase {
5656
XCTAssertEqual(value, instance.authenticationMethod)
5757
}
5858

59+
func testGetServerTrust() {
60+
let registrar = TestProxyApiRegistrar()
61+
let api = registrar.apiDelegate.pigeonApiURLProtectionSpace(registrar)
62+
63+
let instance = TestProtectionSpace(
64+
host: "host", port: 23, protocol: "protocol", realm: "realm", authenticationMethod: "myMethod"
65+
)
66+
let value = try? api.pigeonDelegate.getServerTrust(pigeonApi: api, pigeonInstance: instance)
67+
68+
XCTAssertEqual(value!.value, instance.serverTrust)
69+
}
70+
}
71+
72+
class TestProtectionSpace: URLProtectionSpace, @unchecked Sendable {
73+
var serverTrustVal: SecTrust?
74+
75+
override var serverTrust: SecTrust? {
76+
if serverTrustVal == nil {
77+
let url = FlutterAssetManager().urlForAsset("assets/test_cert.der")!
78+
79+
let certificateData = NSData(contentsOf: url)
80+
let dummyCertificate: SecCertificate! = SecCertificateCreateWithData(nil, certificateData!)
81+
82+
var trust: SecTrust?
83+
SecTrustCreateWithCertificates(
84+
[dummyCertificate] as AnyObject, SecPolicyCreateBasicX509(), &trust)
85+
serverTrustVal = trust!
86+
}
87+
return serverTrustVal
88+
}
5989
}

packages/webview_flutter/webview_flutter_wkwebview/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FlutterAssetManager.swift

+29-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,35 @@
1111
#endif
1212

1313
open class FlutterAssetManager {
14-
func lookupKeyForAsset(_ asset: String) -> String {
14+
let bundle: Bundle
15+
16+
init(bundle: Bundle = Bundle.main) {
17+
self.bundle = bundle
18+
}
19+
20+
func lookupKeyForAsset(_ asset: String) -> String? {
1521
return FlutterDartProject.lookupKey(forAsset: asset)
1622
}
23+
24+
func urlForAsset(_ asset: String) -> URL? {
25+
let assetFilePath: String? = lookupKeyForAsset(asset)
26+
27+
guard let assetFilePath = assetFilePath else {
28+
return nil
29+
}
30+
31+
var url: URL? = bundle.url(
32+
forResource: (assetFilePath as NSString).deletingPathExtension,
33+
withExtension: (assetFilePath as NSString).pathExtension)
34+
35+
#if os(macOS)
36+
// See https://github.com/flutter/flutter/issues/135302
37+
// TODO(stuartmorgan): Remove this if the asset APIs are adjusted to work better for macOS.
38+
if url == nil {
39+
url = URL(string: assetFilePath, relativeTo: bundle.bundleURL)
40+
}
41+
#endif
42+
43+
return url
44+
}
1745
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 Darwin
6+
import Security
7+
8+
/// Data class used to respond to `SecTrustGetTrustResult`.
9+
///
10+
/// The native method needs to return two values, so this custom class is
11+
/// created to support this.
12+
class GetTrustResultResponse {
13+
let result: SecTrustResultType
14+
let resultCode: OSStatus
15+
16+
init(result: SecTrustResultType, resultCode: OSStatus) {
17+
self.result = result
18+
self.resultCode = resultCode
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 Foundation
6+
7+
/// ProxyApi implementation for `GetTrustResultResponse`.
8+
///
9+
/// This class may handle instantiating native object instances that are attached to a Dart instance
10+
/// or handle method calls on the associated native class or an instance of that class.
11+
class GetTrustResultResponseProxyAPIDelegate: PigeonApiDelegateGetTrustResultResponse {
12+
func result(pigeonApi: PigeonApiGetTrustResultResponse, pigeonInstance: GetTrustResultResponse)
13+
throws -> DartSecTrustResultType
14+
{
15+
switch pigeonInstance.result {
16+
case .unspecified:
17+
return .unspecified
18+
case .proceed:
19+
return .proceed
20+
case .deny:
21+
return .deny
22+
case .recoverableTrustFailure:
23+
return .recoverableTrustFailure
24+
case .fatalTrustFailure:
25+
return .fatalTrustFailure
26+
case .otherError:
27+
return .otherError
28+
case .invalid:
29+
return .invalid
30+
case .confirm:
31+
return .confirm
32+
@unknown default:
33+
return .unknown
34+
}
35+
}
36+
37+
func resultCode(
38+
pigeonApi: PigeonApiGetTrustResultResponse, pigeonInstance: GetTrustResultResponse
39+
) throws -> Int64 {
40+
return Int64(pigeonInstance.resultCode)
41+
}
42+
}

0 commit comments

Comments
 (0)