@cap-kit/tls-fingerprint 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapKitTlsFingerprint.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +25 -0
- package/README.md +427 -0
- package/android/build.gradle +103 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/io/capkit/settings/TLSFingerprintImpl.kt +333 -0
- package/android/src/main/java/io/capkit/settings/TLSFingerprintPlugin.kt +342 -0
- package/android/src/main/java/io/capkit/settings/config/TLSFingerprintConfig.kt +102 -0
- package/android/src/main/java/io/capkit/settings/error/TLSFingerprintError.kt +114 -0
- package/android/src/main/java/io/capkit/settings/error/TLSFingerprintErrorMessages.kt +27 -0
- package/android/src/main/java/io/capkit/settings/logger/TLSFingerprintLogger.kt +85 -0
- package/android/src/main/java/io/capkit/settings/model/TLSFingerprintResultModel.kt +32 -0
- package/android/src/main/java/io/capkit/settings/utils/TLSFingerprintUtils.kt +91 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/cli/fingerprint.js +163 -0
- package/dist/cli/fingerprint.js.map +1 -0
- package/dist/docs.json +386 -0
- package/dist/esm/cli/fingerprint.d.ts +1 -0
- package/dist/esm/cli/fingerprint.js +161 -0
- package/dist/esm/cli/fingerprint.js.map +1 -0
- package/dist/esm/definitions.d.ts +244 -0
- package/dist/esm/definitions.js +42 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +13 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/version.d.ts +1 -0
- package/dist/esm/version.js +3 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/esm/web.d.ts +33 -0
- package/dist/esm/web.js +47 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs +107 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.js +110 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/TLSFingerprintPlugin/TLSFingerprintDelegate.swift +365 -0
- package/ios/Sources/TLSFingerprintPlugin/TLSFingerprintImpl.swift +275 -0
- package/ios/Sources/TLSFingerprintPlugin/TLSFingerprintPlugin.swift +219 -0
- package/ios/Sources/TLSFingerprintPlugin/Version.swift +16 -0
- package/ios/Sources/TLSFingerprintPlugin/config/TLSFingerprintConfig.swift +114 -0
- package/ios/Sources/TLSFingerprintPlugin/error/TLSFingerprintError.swift +107 -0
- package/ios/Sources/TLSFingerprintPlugin/error/TLSFingerprintErrorMessages.swift +30 -0
- package/ios/Sources/TLSFingerprintPlugin/logger/TLSFingerprintLogger.swift +69 -0
- package/ios/Sources/TLSFingerprintPlugin/model/TLSFingerprintResult.swift +76 -0
- package/ios/Sources/TLSFingerprintPlugin/utils/TLSFingerprintUtils.swift +79 -0
- package/ios/Tests/TLSFingerprintPluginTests/TLSFingerprintPluginTests.swift +15 -0
- package/package.json +131 -0
- package/scripts/chmod.mjs +34 -0
- package/scripts/sync-version.mjs +68 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Capacitor bridge for the TLSFingerprint plugin.
|
|
6
|
+
|
|
7
|
+
Responsibilities:
|
|
8
|
+
- Parse JS input
|
|
9
|
+
- Delegate to TLSFingerprintImpl
|
|
10
|
+
- Resolve or reject CAPPluginCall
|
|
11
|
+
*/
|
|
12
|
+
@objc(TLSFingerprintPlugin)
|
|
13
|
+
public final class TLSFingerprintPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
14
|
+
|
|
15
|
+
// MARK: - Plugin metadata
|
|
16
|
+
|
|
17
|
+
/// The unique identifier for the plugin.
|
|
18
|
+
public let identifier = "TLSFingerprintPlugin"
|
|
19
|
+
|
|
20
|
+
/// The name used to reference this plugin in JavaScript.
|
|
21
|
+
public let jsName = "TLSFingerprint"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A list of methods exposed by this plugin. These methods can be called from the JavaScript side.
|
|
25
|
+
* - `checkCertificate`: Validates an SSL certificate for a given URL.
|
|
26
|
+
* - `checkCertificates`: Validates multiple SSL certificates for given URLs.
|
|
27
|
+
* - `getPluginVersion`: Retrieves the current version of the plugin.
|
|
28
|
+
*/
|
|
29
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
30
|
+
CAPPluginMethod(name: "checkCertificate", returnType: CAPPluginReturnPromise),
|
|
31
|
+
CAPPluginMethod(name: "checkCertificates", returnType: CAPPluginReturnPromise),
|
|
32
|
+
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
// MARK: - Properties
|
|
36
|
+
|
|
37
|
+
/// Native implementation containing platform-specific logic.
|
|
38
|
+
private let implementation = TLSFingerprintImpl()
|
|
39
|
+
|
|
40
|
+
/// Configuration instance
|
|
41
|
+
private var config: TLSFingerprintConfig?
|
|
42
|
+
|
|
43
|
+
// MARK: - Lifecycle
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
Plugin lifecycle entry point.
|
|
47
|
+
|
|
48
|
+
Called once when the plugin is loaded by the Capacitor bridge.
|
|
49
|
+
This is the correct place to:
|
|
50
|
+
- read configuration values
|
|
51
|
+
- initialize native resources
|
|
52
|
+
- configure the implementation instance
|
|
53
|
+
*/
|
|
54
|
+
override public func load() {
|
|
55
|
+
// Initialize TLSFingerprintConfig with the correct type
|
|
56
|
+
let cfg = TLSFingerprintConfig(plugin: self)
|
|
57
|
+
self.config = cfg
|
|
58
|
+
implementation.applyConfig(cfg)
|
|
59
|
+
|
|
60
|
+
// Log if verbose logging is enabled
|
|
61
|
+
TLSFingerprintLogger.debug("Plugin loaded. Version: ", PluginVersion.number)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// MARK: - Error Mapping
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Rejects the call using standardized error codes from the native TLSFingerprintError enum.
|
|
68
|
+
*/
|
|
69
|
+
private func reject(
|
|
70
|
+
_ call: CAPPluginCall,
|
|
71
|
+
error: TLSFingerprintError
|
|
72
|
+
) {
|
|
73
|
+
// Use the centralized errorCode and message defined in TLSFingerprintError.swift
|
|
74
|
+
call.reject(error.message, error.errorCode)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private func handleError(_ call: CAPPluginCall, _ error: Error) {
|
|
78
|
+
if let settingsError = error as? TLSFingerprintError {
|
|
79
|
+
call.reject(settingsError.message, settingsError.errorCode)
|
|
80
|
+
} else {
|
|
81
|
+
reject(call, error: .initFailed(error.localizedDescription))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// MARK: - SSL Pinning (single fingerprint)
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
Validates the SSL certificate of a HTTPS endpoint
|
|
89
|
+
using a single fingerprint.
|
|
90
|
+
*/
|
|
91
|
+
@objc func checkCertificate(_ call: CAPPluginCall) {
|
|
92
|
+
let url = call.getString("url", "")
|
|
93
|
+
let fingerprintValue = call.getString("fingerprint")
|
|
94
|
+
let fingerprint =
|
|
95
|
+
fingerprintValue?.isEmpty == false
|
|
96
|
+
? fingerprintValue
|
|
97
|
+
: nil
|
|
98
|
+
|
|
99
|
+
guard !url.isEmpty else {
|
|
100
|
+
call.reject(
|
|
101
|
+
TLSFingerprintErrorMessages.urlRequired,
|
|
102
|
+
"INVALID_INPUT"
|
|
103
|
+
)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
guard let urlObj = URL(string: url), let host = urlObj.host, !host.isEmpty else {
|
|
108
|
+
call.reject(
|
|
109
|
+
TLSFingerprintErrorMessages.noHostFoundInUrl,
|
|
110
|
+
"INVALID_INPUT"
|
|
111
|
+
)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if let fp = fingerprint, !TLSFingerprintUtils.isValidFingerprintFormat(fp) {
|
|
116
|
+
call.reject(
|
|
117
|
+
TLSFingerprintErrorMessages.invalidFingerprintFormat,
|
|
118
|
+
"INVALID_INPUT"
|
|
119
|
+
)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Task {
|
|
124
|
+
do {
|
|
125
|
+
let result =
|
|
126
|
+
try await implementation.checkCertificate(
|
|
127
|
+
urlString: url,
|
|
128
|
+
fingerprintFromArgs: fingerprint
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
// Converting Swift TLSFingerprintResult to JSObject
|
|
132
|
+
call.resolve(result.toDictionary())
|
|
133
|
+
} catch let error as TLSFingerprintError {
|
|
134
|
+
self.reject(call, error: error)
|
|
135
|
+
} catch {
|
|
136
|
+
call.reject(
|
|
137
|
+
error.localizedDescription,
|
|
138
|
+
"INIT_FAILED"
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// MARK: - SSL Pinning (multiple fingerprints)
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
Validates the SSL certificate of a HTTPS endpoint
|
|
148
|
+
using multiple allowed fingerprints.
|
|
149
|
+
*/
|
|
150
|
+
@objc func checkCertificates(_ call: CAPPluginCall) {
|
|
151
|
+
let url = call.getString("url", "")
|
|
152
|
+
|
|
153
|
+
// Extraction and filtering of optional fingerprints
|
|
154
|
+
let fingerprints =
|
|
155
|
+
call.getArray("fingerprints")?
|
|
156
|
+
.compactMap { $0 as? String }
|
|
157
|
+
.filter { !$0.isEmpty }
|
|
158
|
+
|
|
159
|
+
guard !url.isEmpty else {
|
|
160
|
+
call.reject(
|
|
161
|
+
TLSFingerprintErrorMessages.urlRequired,
|
|
162
|
+
"INVALID_INPUT"
|
|
163
|
+
)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
guard let urlObj = URL(string: url), let host = urlObj.host, !host.isEmpty else {
|
|
168
|
+
call.reject(
|
|
169
|
+
TLSFingerprintErrorMessages.noHostFoundInUrl,
|
|
170
|
+
"INVALID_INPUT"
|
|
171
|
+
)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if let fps = fingerprints {
|
|
176
|
+
for fp in fps {
|
|
177
|
+
if !TLSFingerprintUtils.isValidFingerprintFormat(fp) {
|
|
178
|
+
call.reject(
|
|
179
|
+
TLSFingerprintErrorMessages.invalidFingerprintFormat,
|
|
180
|
+
"INVALID_INPUT"
|
|
181
|
+
)
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
Task {
|
|
188
|
+
do {
|
|
189
|
+
let result =
|
|
190
|
+
try await implementation.checkCertificates(
|
|
191
|
+
urlString: url,
|
|
192
|
+
fingerprintsFromArgs:
|
|
193
|
+
fingerprints?.isEmpty == false
|
|
194
|
+
? fingerprints
|
|
195
|
+
: nil
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
call.resolve(result.toDictionary())
|
|
199
|
+
} catch let error as TLSFingerprintError {
|
|
200
|
+
self.reject(call, error: error)
|
|
201
|
+
} catch {
|
|
202
|
+
call.reject(
|
|
203
|
+
error.localizedDescription,
|
|
204
|
+
"INIT_FAILED"
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// MARK: - Version
|
|
211
|
+
|
|
212
|
+
/// Retrieves the plugin version synchronized from package.json.
|
|
213
|
+
@objc func getPluginVersion(_ call: CAPPluginCall) {
|
|
214
|
+
// Standardized enum name across all CapKit plugins
|
|
215
|
+
call.resolve([
|
|
216
|
+
"version": PluginVersion.number
|
|
217
|
+
])
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// This file is automatically generated. Do not modify manually.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Container for the plugin's version information.
|
|
6
|
+
This enum provides a centralized, single source of truth for the native
|
|
7
|
+
version string, synchronized directly from the project's 'package.json'.
|
|
8
|
+
It ensures parity across JavaScript, Android, and iOS platforms.
|
|
9
|
+
*/
|
|
10
|
+
public enum PluginVersion {
|
|
11
|
+
/**
|
|
12
|
+
The semantic version string of the plugin.
|
|
13
|
+
Value synchronized from package.json: "8.0.0"
|
|
14
|
+
*/
|
|
15
|
+
public static let number = "8.0.0"
|
|
16
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Plugin configuration container.
|
|
6
|
+
|
|
7
|
+
This struct is responsible for reading and exposing
|
|
8
|
+
static configuration values defined under the
|
|
9
|
+
`TLSFingerprint` key in capacitor.config.ts.
|
|
10
|
+
|
|
11
|
+
Configuration rules:
|
|
12
|
+
- Read once during plugin initialization
|
|
13
|
+
- Treated as immutable runtime input
|
|
14
|
+
- Accessible only from native code
|
|
15
|
+
*/
|
|
16
|
+
public struct TLSFingerprintConfig {
|
|
17
|
+
|
|
18
|
+
// MARK: - Configuration Keys
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
Centralized definition of configuration keys.
|
|
22
|
+
Avoids string duplication and typos.
|
|
23
|
+
*/
|
|
24
|
+
private enum Keys {
|
|
25
|
+
static let verboseLogging = "verboseLogging"
|
|
26
|
+
static let fingerprint = "fingerprint"
|
|
27
|
+
static let fingerprints = "fingerprints"
|
|
28
|
+
static let excludedDomains = "excludedDomains"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// MARK: - Public Configuration Values
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
Enables verbose native logging.
|
|
35
|
+
|
|
36
|
+
When enabled, the plugin prints additional
|
|
37
|
+
debug information to the Xcode console.
|
|
38
|
+
|
|
39
|
+
Default: false
|
|
40
|
+
*/
|
|
41
|
+
public let verboseLogging: Bool
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
Default SHA-256 fingerprint used by `checkCertificate()`
|
|
45
|
+
when no fingerprint is provided at runtime.
|
|
46
|
+
*/
|
|
47
|
+
public let fingerprint: String?
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
Default SHA-256 fingerprints used by `checkCertificates()`
|
|
51
|
+
when no fingerprints are provided at runtime.
|
|
52
|
+
*/
|
|
53
|
+
public let fingerprints: [String]
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
Domains or URL prefixes excluded from SSL pinning.
|
|
57
|
+
|
|
58
|
+
Any request whose host matches one of these values
|
|
59
|
+
MUST bypass SSL pinning checks.
|
|
60
|
+
*/
|
|
61
|
+
public let excludedDomains: [String]
|
|
62
|
+
|
|
63
|
+
// MARK: - Defaults
|
|
64
|
+
|
|
65
|
+
private static let defaultVerboseLogging: Bool = false
|
|
66
|
+
private static let defaultFingerprint: String? = nil
|
|
67
|
+
private static let defaultFingerprints: [String] = []
|
|
68
|
+
private static let defaultExcludedDomains: [String] = []
|
|
69
|
+
|
|
70
|
+
// MARK: - Initialization
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
Initializes the configuration by reading values
|
|
74
|
+
from the Capacitor PluginConfig.
|
|
75
|
+
|
|
76
|
+
- Parameter plugin: The CAPPlugin instance used
|
|
77
|
+
to access typed configuration values.
|
|
78
|
+
*/
|
|
79
|
+
init(plugin: CAPPlugin) {
|
|
80
|
+
let config = plugin.getConfig()
|
|
81
|
+
|
|
82
|
+
// Verbose logging flag
|
|
83
|
+
self.verboseLogging =
|
|
84
|
+
config.getBoolean(
|
|
85
|
+
Keys.verboseLogging,
|
|
86
|
+
Self.defaultVerboseLogging
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Synchronize logger state
|
|
90
|
+
TLSFingerprintLogger.verbose = self.verboseLogging
|
|
91
|
+
|
|
92
|
+
// Single fingerprint (optional)
|
|
93
|
+
if let value = config.getString(Keys.fingerprint),
|
|
94
|
+
!value.isEmpty {
|
|
95
|
+
self.fingerprint = value
|
|
96
|
+
} else {
|
|
97
|
+
self.fingerprint = Self.defaultFingerprint
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Multiple fingerprints (optional)
|
|
101
|
+
self.fingerprints =
|
|
102
|
+
config.getArray(Keys.fingerprints)?
|
|
103
|
+
.compactMap { $0 as? String }
|
|
104
|
+
.filter { !$0.isEmpty }
|
|
105
|
+
?? Self.defaultFingerprints
|
|
106
|
+
|
|
107
|
+
// Excluded domains (optional)
|
|
108
|
+
self.excludedDomains =
|
|
109
|
+
config.getArray(Keys.excludedDomains)?
|
|
110
|
+
.compactMap { $0 as? String }
|
|
111
|
+
.filter { !$0.isEmpty }
|
|
112
|
+
?? Self.defaultExcludedDomains
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Native error model for the TLSFingerprint plugin (iOS).
|
|
5
|
+
|
|
6
|
+
This enum represents all error categories that can be
|
|
7
|
+
produced by the native implementation layer.
|
|
8
|
+
|
|
9
|
+
Architectural rules:
|
|
10
|
+
- Must NOT reference Capacitor
|
|
11
|
+
- Must NOT reference JavaScript
|
|
12
|
+
- Must be throwable from the Impl layer
|
|
13
|
+
- Mapping to JS-facing error codes happens ONLY in the Plugin layer
|
|
14
|
+
*/
|
|
15
|
+
enum TLSFingerprintError: Error {
|
|
16
|
+
|
|
17
|
+
/// Feature or capability is not available on this device or configuration
|
|
18
|
+
case unavailable(String)
|
|
19
|
+
|
|
20
|
+
/// The user cancelled an interactive flow
|
|
21
|
+
case cancelled(String)
|
|
22
|
+
|
|
23
|
+
/// Required permission was denied or not granted
|
|
24
|
+
case permissionDenied(String)
|
|
25
|
+
|
|
26
|
+
/// Plugin failed to initialize or perform a required operation
|
|
27
|
+
case initFailed(String)
|
|
28
|
+
|
|
29
|
+
/// The input provided to the plugin method is invalid or malformed
|
|
30
|
+
case invalidInput(String)
|
|
31
|
+
|
|
32
|
+
/// Invalid or unsupported input was provided
|
|
33
|
+
case unknownType(String)
|
|
34
|
+
|
|
35
|
+
/// The requested resource does not exist
|
|
36
|
+
case notFound(String)
|
|
37
|
+
|
|
38
|
+
/// The operation conflicts with the current state
|
|
39
|
+
case conflict(String)
|
|
40
|
+
|
|
41
|
+
/// The operation did not complete within the expected time
|
|
42
|
+
case timeout(String)
|
|
43
|
+
|
|
44
|
+
/// The certificate fingerprint did not match any expected fingerprint
|
|
45
|
+
case pinningFailed(String)
|
|
46
|
+
|
|
47
|
+
/// SSL/TLS specific error (certificate expired, handshake failure, etc.)
|
|
48
|
+
case sslError(String)
|
|
49
|
+
|
|
50
|
+
/// Invalid or malformed configuration
|
|
51
|
+
case invalidConfig(String)
|
|
52
|
+
|
|
53
|
+
// MARK: - Human-readable message
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
Human-readable error message.
|
|
57
|
+
|
|
58
|
+
This message is intended to be passed verbatim
|
|
59
|
+
to JavaScript via `call.reject(message, code)`.
|
|
60
|
+
*/
|
|
61
|
+
var message: String {
|
|
62
|
+
switch self {
|
|
63
|
+
case .unavailable(let message):
|
|
64
|
+
return message
|
|
65
|
+
case .cancelled(let message):
|
|
66
|
+
return message
|
|
67
|
+
case .permissionDenied(let message):
|
|
68
|
+
return message
|
|
69
|
+
case .initFailed(let message):
|
|
70
|
+
return message
|
|
71
|
+
case .invalidInput(let message):
|
|
72
|
+
return message
|
|
73
|
+
case .unknownType(let message):
|
|
74
|
+
return message
|
|
75
|
+
case .notFound(let message):
|
|
76
|
+
return message
|
|
77
|
+
case .conflict(let message):
|
|
78
|
+
return message
|
|
79
|
+
case .timeout(let message):
|
|
80
|
+
return message
|
|
81
|
+
case .pinningFailed(let message):
|
|
82
|
+
return message
|
|
83
|
+
case .sslError(let message):
|
|
84
|
+
return message
|
|
85
|
+
case .invalidConfig(let message):
|
|
86
|
+
return message
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Standardized error code string for JS rejection.
|
|
91
|
+
var errorCode: String {
|
|
92
|
+
switch self {
|
|
93
|
+
case .unavailable: return "UNAVAILABLE"
|
|
94
|
+
case .cancelled: return "CANCELLED"
|
|
95
|
+
case .permissionDenied: return "PERMISSION_DENIED"
|
|
96
|
+
case .initFailed: return "INIT_FAILED"
|
|
97
|
+
case .invalidInput: return "INVALID_INPUT"
|
|
98
|
+
case .unknownType: return "UNKNOWN_TYPE"
|
|
99
|
+
case .notFound: return "NOT_FOUND"
|
|
100
|
+
case .conflict: return "CONFLICT"
|
|
101
|
+
case .timeout: return "TIMEOUT"
|
|
102
|
+
case .pinningFailed: return "PINNING_FAILED"
|
|
103
|
+
case .sslError: return "SSL_ERROR"
|
|
104
|
+
case .invalidConfig: return "INVALID_INPUT"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Canonical error messages shared across platforms.
|
|
5
|
+
|
|
6
|
+
These strings must remain byte-identical on iOS and Android.
|
|
7
|
+
*/
|
|
8
|
+
enum TLSFingerprintErrorMessages {
|
|
9
|
+
static let urlRequired = "url is required"
|
|
10
|
+
static let invalidUrlMustBeHttps = "Invalid URL. Must be https."
|
|
11
|
+
static let invalidUrl = "Invalid URL."
|
|
12
|
+
static let noFingerprintsProvided = "No fingerprints provided"
|
|
13
|
+
static let noHostFoundInUrl = "No host found in URL"
|
|
14
|
+
static let invalidFingerprintFormat = "Invalid fingerprint format"
|
|
15
|
+
static let unsupportedHost = "Unsupported host: %@"
|
|
16
|
+
static let pinningFailed = "Pinning failed"
|
|
17
|
+
static let excludedDomain = "Excluded domain"
|
|
18
|
+
static let timeout = "Timeout"
|
|
19
|
+
static let networkError = "Network error"
|
|
20
|
+
static let internalError = "Internal error"
|
|
21
|
+
static let invalidConfig = "Invalid configuration: %@"
|
|
22
|
+
|
|
23
|
+
static func unsupportedHost(_ value: String) -> String {
|
|
24
|
+
return String(format: unsupportedHost, value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static func invalidConfig(_ value: String) -> String {
|
|
28
|
+
return String(format: invalidConfig, value)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Centralized logger for the TLSFingerprint plugin.
|
|
5
|
+
|
|
6
|
+
Responsibilities:
|
|
7
|
+
- Provide a single logging entry point
|
|
8
|
+
- Support runtime-controlled verbose logging
|
|
9
|
+
- Keep logging behavior consistent across files
|
|
10
|
+
|
|
11
|
+
Forbidden:
|
|
12
|
+
- Controlling application logic
|
|
13
|
+
- Being queried for flow decisions
|
|
14
|
+
*/
|
|
15
|
+
enum TLSFingerprintLogger {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
Controls whether debug logs are printed.
|
|
19
|
+
|
|
20
|
+
This value MUST be set once during plugin initialization
|
|
21
|
+
based on static configuration.
|
|
22
|
+
*/
|
|
23
|
+
static var verbose: Bool = false
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
Prints a verbose / debug log message.
|
|
27
|
+
|
|
28
|
+
Debug logs are automatically silenced
|
|
29
|
+
when `verbose` is false.
|
|
30
|
+
*/
|
|
31
|
+
static func debug(_ items: Any...) {
|
|
32
|
+
guard verbose else { return }
|
|
33
|
+
log(items)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
Prints an error log message.
|
|
38
|
+
|
|
39
|
+
Error logs are always printed regardless
|
|
40
|
+
of the verbose flag.
|
|
41
|
+
*/
|
|
42
|
+
static func error(_ items: Any...) {
|
|
43
|
+
log(items)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// MARK: - Internal log printer
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
Low-level log printer with a consistent prefix.
|
|
51
|
+
|
|
52
|
+
This function MUST NOT be used outside this file.
|
|
53
|
+
*/
|
|
54
|
+
private func log(
|
|
55
|
+
_ items: [Any],
|
|
56
|
+
separator: String = " ",
|
|
57
|
+
terminator: String = "\n"
|
|
58
|
+
) {
|
|
59
|
+
CAPLog.print("⚡️ TLSFingerprint -", terminator: separator)
|
|
60
|
+
|
|
61
|
+
for (index, item) in items.enumerated() {
|
|
62
|
+
CAPLog.print(
|
|
63
|
+
item,
|
|
64
|
+
terminator: index == items.count - 1
|
|
65
|
+
? terminator
|
|
66
|
+
: separator
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Canonical result model for TLS fingerprint validation operations on iOS.
|
|
5
|
+
|
|
6
|
+
This struct is exchanged between the native implementation (TLSFingerprintImpl)
|
|
7
|
+
and the bridge (TLSFingerprintPlugin) and serialized to JavaScript via a JSObject.
|
|
8
|
+
|
|
9
|
+
Fields mirror the public JS payload:
|
|
10
|
+
- actualFingerprint: server fingerprint used for matching
|
|
11
|
+
- fingerprintMatched: whether the fingerprint check succeeded (true) or not (false)
|
|
12
|
+
- matchedFingerprint: the fingerprint that matched (only present for fingerprint mode)
|
|
13
|
+
- excludedDomain: indicates an excluded-domain bypass (true when applicable)
|
|
14
|
+
- mode: active mode: "fingerprint" | "excluded"
|
|
15
|
+
- error: human-readable error (empty on success/match)
|
|
16
|
+
- errorCode: canonical error code string (empty on success)
|
|
17
|
+
*/
|
|
18
|
+
struct TLSFingerprintResult {
|
|
19
|
+
let actualFingerprint: String?
|
|
20
|
+
let fingerprintMatched: Bool
|
|
21
|
+
let matchedFingerprint: String?
|
|
22
|
+
let excludedDomain: Bool?
|
|
23
|
+
let mode: String?
|
|
24
|
+
let error: String?
|
|
25
|
+
let errorCode: String?
|
|
26
|
+
|
|
27
|
+
init(
|
|
28
|
+
actualFingerprint: String? = nil,
|
|
29
|
+
fingerprintMatched: Bool,
|
|
30
|
+
matchedFingerprint: String? = nil,
|
|
31
|
+
excludedDomain: Bool? = nil,
|
|
32
|
+
mode: String? = nil,
|
|
33
|
+
error: String? = nil,
|
|
34
|
+
errorCode: String? = nil
|
|
35
|
+
) {
|
|
36
|
+
self.actualFingerprint = actualFingerprint
|
|
37
|
+
self.fingerprintMatched = fingerprintMatched
|
|
38
|
+
self.matchedFingerprint = matchedFingerprint
|
|
39
|
+
self.excludedDomain = excludedDomain
|
|
40
|
+
self.mode = mode
|
|
41
|
+
self.error = error
|
|
42
|
+
self.errorCode = errorCode
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func toDictionary() -> [String: Any] {
|
|
46
|
+
var dict: [String: Any] = [
|
|
47
|
+
"fingerprintMatched": fingerprintMatched
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
if let actualFingerprint = actualFingerprint {
|
|
51
|
+
dict["actualFingerprint"] = actualFingerprint
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if let matchedFingerprint = matchedFingerprint {
|
|
55
|
+
dict["matchedFingerprint"] = matchedFingerprint
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if let excludedDomain = excludedDomain {
|
|
59
|
+
dict["excludedDomain"] = excludedDomain
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if let mode = mode {
|
|
63
|
+
dict["mode"] = mode
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if let error = error {
|
|
67
|
+
dict["error"] = error
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if let errorCode = errorCode {
|
|
71
|
+
dict["errorCode"] = errorCode
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return dict
|
|
75
|
+
}
|
|
76
|
+
}
|