@cap-kit/ssl-pinning 8.0.0-next.0 → 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/Package.swift +1 -1
- package/README.md +232 -372
- package/android/src/main/java/io/capkit/sslpinning/SSLPinningConfig.kt +48 -3
- package/android/src/main/java/io/capkit/sslpinning/SSLPinningError.kt +40 -0
- package/android/src/main/java/io/capkit/sslpinning/SSLPinningImpl.kt +137 -90
- package/android/src/main/java/io/capkit/sslpinning/SSLPinningPlugin.kt +146 -29
- package/android/src/main/java/io/capkit/sslpinning/utils/SSLPinningLogger.kt +30 -38
- package/android/src/main/java/io/capkit/sslpinning/utils/SSLPinningUtils.kt +25 -9
- package/dist/docs.json +29 -204
- package/dist/esm/definitions.d.ts +74 -177
- package/dist/esm/definitions.js +12 -6
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +9 -22
- package/dist/esm/web.js +5 -5
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +17 -11
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +17 -11
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/SSLPinningPlugin/SSLPinningConfig.swift +45 -30
- package/ios/Sources/SSLPinningPlugin/SSLPinningDelegate.swift +83 -26
- package/ios/Sources/SSLPinningPlugin/SSLPinningError.swift +49 -0
- package/ios/Sources/SSLPinningPlugin/SSLPinningImpl.swift +94 -64
- package/ios/Sources/SSLPinningPlugin/SSLPinningPlugin.swift +121 -50
- package/ios/Sources/SSLPinningPlugin/Utils/SSLPinningLogger.swift +28 -16
- package/ios/Sources/SSLPinningPlugin/Utils/SSLPinningUtils.swift +55 -18
- package/ios/Sources/SSLPinningPlugin/Version.swift +2 -2
- package/package.json +5 -6
|
@@ -1,22 +1,67 @@
|
|
|
1
1
|
package io.capkit.sslpinning
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
3
4
|
import com.getcapacitor.Plugin
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Plugin configuration container.
|
|
8
|
+
*
|
|
9
|
+
* This class is responsible for reading and exposing
|
|
10
|
+
* static configuration values defined under the
|
|
11
|
+
* `SSLPinning` key in capacitor.config.ts.
|
|
12
|
+
*
|
|
13
|
+
* Configuration rules:
|
|
14
|
+
* - Read once during plugin initialization
|
|
15
|
+
* - Treated as immutable runtime input
|
|
16
|
+
* - Accessible only from native code
|
|
17
|
+
*/
|
|
5
18
|
class SSLPinningConfig(plugin: Plugin) {
|
|
19
|
+
/**
|
|
20
|
+
* Android application context.
|
|
21
|
+
* Exposed for native components that may require it.
|
|
22
|
+
*/
|
|
23
|
+
val context: Context = plugin.context
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Enables verbose native logging.
|
|
27
|
+
*
|
|
28
|
+
* When enabled, additional debug information
|
|
29
|
+
* is printed to Logcat.
|
|
30
|
+
*
|
|
31
|
+
* Default: false
|
|
32
|
+
*/
|
|
6
33
|
val verboseLogging: Boolean
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default SHA-256 fingerprint used by checkCertificate()
|
|
37
|
+
* when no fingerprint is provided at runtime.
|
|
38
|
+
*/
|
|
7
39
|
val fingerprint: String?
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Default SHA-256 fingerprints used by checkCertificates()
|
|
43
|
+
* when no fingerprints are provided at runtime.
|
|
44
|
+
*/
|
|
8
45
|
val fingerprints: List<String>
|
|
9
46
|
|
|
10
47
|
init {
|
|
11
48
|
val config = plugin.getConfig()
|
|
12
49
|
|
|
13
|
-
|
|
50
|
+
// Verbose logging flag
|
|
51
|
+
verboseLogging =
|
|
52
|
+
config.getBoolean("verboseLogging", false)
|
|
14
53
|
|
|
54
|
+
// Single fingerprint (optional)
|
|
15
55
|
val fp = config.getString("fingerprint")
|
|
16
|
-
fingerprint =
|
|
56
|
+
fingerprint =
|
|
57
|
+
if (!fp.isNullOrBlank()) fp else null
|
|
17
58
|
|
|
59
|
+
// Multiple fingerprints (optional)
|
|
18
60
|
fingerprints =
|
|
19
|
-
config.getArray("fingerprints")
|
|
61
|
+
config.getArray("fingerprints")
|
|
62
|
+
?.toList()
|
|
63
|
+
?.mapNotNull { it as? String }
|
|
64
|
+
?.filter { it.isNotBlank() }
|
|
20
65
|
?: emptyList()
|
|
21
66
|
}
|
|
22
67
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package io.capkit.sslpinning
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native error model for the SSLPinning plugin (Android).
|
|
5
|
+
*
|
|
6
|
+
* Architectural rules:
|
|
7
|
+
* - Must NOT reference Capacitor APIs
|
|
8
|
+
* - Must NOT reference JavaScript
|
|
9
|
+
* - Must be throwable from the Impl layer
|
|
10
|
+
* - Mapping to JS-facing error codes happens ONLY in the Plugin layer
|
|
11
|
+
*/
|
|
12
|
+
sealed class SSLPinningError(
|
|
13
|
+
message: String,
|
|
14
|
+
) : Throwable(message) {
|
|
15
|
+
/**
|
|
16
|
+
* Feature or capability is not available
|
|
17
|
+
* due to device or configuration limitations.
|
|
18
|
+
*/
|
|
19
|
+
class Unavailable(message: String) :
|
|
20
|
+
SSLPinningError(message)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Required permission was denied or not granted.
|
|
24
|
+
*/
|
|
25
|
+
class PermissionDenied(message: String) :
|
|
26
|
+
SSLPinningError(message)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Plugin failed to initialize or perform
|
|
30
|
+
* a required operation.
|
|
31
|
+
*/
|
|
32
|
+
class InitFailed(message: String) :
|
|
33
|
+
SSLPinningError(message)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Invalid or unsupported input was provided.
|
|
37
|
+
*/
|
|
38
|
+
class UnknownType(message: String) :
|
|
39
|
+
SSLPinningError(message)
|
|
40
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package io.capkit.sslpinning
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
3
4
|
import io.capkit.sslpinning.utils.SSLPinningLogger
|
|
4
5
|
import io.capkit.sslpinning.utils.SSLPinningUtils
|
|
5
6
|
import java.net.URL
|
|
@@ -9,157 +10,191 @@ import javax.net.ssl.SSLContext
|
|
|
9
10
|
import javax.net.ssl.TrustManager
|
|
10
11
|
import javax.net.ssl.X509TrustManager
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Native Android implementation for the SSLPinning plugin.
|
|
15
|
+
*
|
|
16
|
+
* Responsibilities:
|
|
17
|
+
* - Perform platform-specific SSL pinning logic
|
|
18
|
+
* - Interact with system networking APIs
|
|
19
|
+
* - Throw typed SSLPinningError values on failure
|
|
20
|
+
*
|
|
21
|
+
* Forbidden:
|
|
22
|
+
* - Accessing PluginCall
|
|
23
|
+
* - Referencing Capacitor APIs
|
|
24
|
+
* - Constructing JavaScript payloads
|
|
25
|
+
*/
|
|
12
26
|
class SSLPinningImpl(
|
|
13
|
-
private val
|
|
27
|
+
private val context: Context,
|
|
14
28
|
) {
|
|
15
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Cached plugin configuration.
|
|
31
|
+
* Injected once during plugin initialization.
|
|
32
|
+
*/
|
|
33
|
+
private lateinit var config: SSLPinningConfig
|
|
16
34
|
|
|
17
35
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* This method:
|
|
21
|
-
* - Opens a TLS connection to the given URL
|
|
22
|
-
* - Extracts the leaf certificate presented by the server
|
|
23
|
-
* - Computes its SHA-256 fingerprint
|
|
24
|
-
* - Compares it against the provided or configured fingerprint
|
|
36
|
+
* Applies plugin configuration.
|
|
25
37
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
38
|
+
* This method MUST be called exactly once from the plugin's load() method.
|
|
39
|
+
* It translates static configuration into runtime behavior
|
|
40
|
+
* (e.g. enabling verbose logging).
|
|
41
|
+
*/
|
|
42
|
+
fun updateConfig(newConfig: SSLPinningConfig) {
|
|
43
|
+
this.config = newConfig
|
|
44
|
+
SSLPinningLogger.verbose = newConfig.verboseLogging
|
|
45
|
+
SSLPinningLogger.debug(
|
|
46
|
+
"Configuration applied. Verbose logging:",
|
|
47
|
+
newConfig.verboseLogging.toString(),
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Single fingerprint
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validates the SSL certificate of a HTTPS endpoint
|
|
57
|
+
* using a single SHA-256 fingerprint.
|
|
30
58
|
*/
|
|
59
|
+
@Throws(SSLPinningError::class)
|
|
31
60
|
fun checkCertificate(
|
|
32
61
|
urlString: String,
|
|
33
62
|
fingerprintFromArgs: String?,
|
|
34
|
-
|
|
35
|
-
) {
|
|
63
|
+
): Map<String, Any> {
|
|
36
64
|
val fingerprint =
|
|
37
65
|
fingerprintFromArgs ?: config.fingerprint
|
|
38
66
|
|
|
39
67
|
if (fingerprint == null) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"fingerprintMatched" to false,
|
|
43
|
-
"error" to "No fingerprint provided (args or config)",
|
|
44
|
-
),
|
|
68
|
+
throw SSLPinningError.Unavailable(
|
|
69
|
+
"No fingerprint provided (args or config)",
|
|
45
70
|
)
|
|
46
|
-
return
|
|
47
71
|
}
|
|
48
72
|
|
|
49
|
-
performCheck(
|
|
73
|
+
return performCheck(
|
|
74
|
+
urlString = urlString,
|
|
75
|
+
fingerprints = listOf(fingerprint),
|
|
76
|
+
)
|
|
50
77
|
}
|
|
51
78
|
|
|
52
|
-
//
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Multiple fingerprints
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
53
82
|
|
|
54
83
|
/**
|
|
55
|
-
* Validates the SSL certificate of a HTTPS endpoint
|
|
56
|
-
*
|
|
57
|
-
* The certificate is considered valid if **any** of the provided fingerprints
|
|
58
|
-
* matches the server's leaf certificate fingerprint.
|
|
59
|
-
*
|
|
60
|
-
* This is typically used to support certificate rotation.
|
|
84
|
+
* Validates the SSL certificate of a HTTPS endpoint
|
|
85
|
+
* using multiple allowed SHA-256 fingerprints.
|
|
61
86
|
*/
|
|
87
|
+
@Throws(SSLPinningError::class)
|
|
62
88
|
fun checkCertificates(
|
|
63
89
|
urlString: String,
|
|
64
90
|
fingerprintsFromArgs: List<String>?,
|
|
65
|
-
|
|
66
|
-
) {
|
|
91
|
+
): Map<String, Any> {
|
|
67
92
|
val fingerprints =
|
|
68
93
|
fingerprintsFromArgs?.takeIf { it.isNotEmpty() }
|
|
69
94
|
?: config.fingerprints.takeIf { it.isNotEmpty() }
|
|
70
95
|
|
|
71
96
|
if (fingerprints == null) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"fingerprintMatched" to false,
|
|
75
|
-
"error" to "No fingerprints provided (args or config)",
|
|
76
|
-
),
|
|
97
|
+
throw SSLPinningError.Unavailable(
|
|
98
|
+
"No fingerprints provided (args or config)",
|
|
77
99
|
)
|
|
78
|
-
return
|
|
79
100
|
}
|
|
80
101
|
|
|
81
|
-
performCheck(
|
|
102
|
+
return performCheck(
|
|
103
|
+
urlString = urlString,
|
|
104
|
+
fingerprints = fingerprints,
|
|
105
|
+
)
|
|
82
106
|
}
|
|
83
107
|
|
|
84
|
-
//
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Shared implementation
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
85
111
|
|
|
86
112
|
/**
|
|
87
|
-
*
|
|
113
|
+
* Performs the actual SSL pinning validation.
|
|
114
|
+
*
|
|
115
|
+
* This method:
|
|
116
|
+
* - Validates the HTTPS URL
|
|
117
|
+
* - Opens a TLS connection
|
|
118
|
+
* - Extracts the server leaf certificate
|
|
119
|
+
* - Compares its SHA-256 fingerprint
|
|
120
|
+
* against the expected ones
|
|
88
121
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
122
|
+
* IMPORTANT:
|
|
123
|
+
* - The system trust chain is NOT evaluated
|
|
124
|
+
* - Only fingerprint matching determines acceptance
|
|
92
125
|
*/
|
|
126
|
+
@Throws(SSLPinningError::class)
|
|
93
127
|
private fun performCheck(
|
|
94
128
|
urlString: String,
|
|
95
129
|
fingerprints: List<String>,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
),
|
|
106
|
-
)
|
|
107
|
-
return
|
|
108
|
-
}
|
|
130
|
+
): Map<String, Any> {
|
|
131
|
+
val url =
|
|
132
|
+
SSLPinningUtils.httpsUrl(urlString)
|
|
133
|
+
?: throw SSLPinningError.UnknownType(
|
|
134
|
+
"Invalid HTTPS URL",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return try {
|
|
138
|
+
val certificate = getCertificate(url)
|
|
109
139
|
|
|
110
|
-
try {
|
|
111
|
-
val cert = getCertificate(url)
|
|
112
140
|
val actualFingerprint =
|
|
113
141
|
SSLPinningUtils.normalizeFingerprint(
|
|
114
|
-
SSLPinningUtils.sha256Fingerprint(
|
|
142
|
+
SSLPinningUtils.sha256Fingerprint(certificate),
|
|
115
143
|
)
|
|
116
144
|
|
|
117
145
|
val normalizedExpected =
|
|
118
|
-
fingerprints.map {
|
|
146
|
+
fingerprints.map {
|
|
147
|
+
SSLPinningUtils.normalizeFingerprint(it)
|
|
148
|
+
}
|
|
119
149
|
|
|
120
150
|
val matchedFingerprint =
|
|
121
|
-
normalizedExpected.firstOrNull {
|
|
151
|
+
normalizedExpected.firstOrNull {
|
|
152
|
+
it == actualFingerprint
|
|
153
|
+
}
|
|
122
154
|
|
|
123
155
|
val matched = matchedFingerprint != null
|
|
124
156
|
|
|
125
|
-
SSLPinningLogger.debug(
|
|
157
|
+
SSLPinningLogger.debug(
|
|
158
|
+
"SSLPinning matched:",
|
|
159
|
+
matched.toString(),
|
|
160
|
+
)
|
|
126
161
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"matchedFingerprint" to (matchedFingerprint ?: ""),
|
|
132
|
-
),
|
|
162
|
+
mapOf(
|
|
163
|
+
"actualFingerprint" to actualFingerprint,
|
|
164
|
+
"fingerprintMatched" to matched,
|
|
165
|
+
"matchedFingerprint" to (matchedFingerprint ?: ""),
|
|
133
166
|
)
|
|
167
|
+
} catch (e: SSLPinningError) {
|
|
168
|
+
throw e
|
|
134
169
|
} catch (e: Exception) {
|
|
135
|
-
SSLPinningLogger.error(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
),
|
|
170
|
+
SSLPinningLogger.error(
|
|
171
|
+
"Certificate check failed",
|
|
172
|
+
e,
|
|
173
|
+
)
|
|
174
|
+
throw SSLPinningError.InitFailed(
|
|
175
|
+
e.message ?: "SSL pinning failed",
|
|
142
176
|
)
|
|
143
177
|
}
|
|
144
178
|
}
|
|
145
179
|
|
|
146
|
-
//
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Certificate retrieval
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
147
183
|
|
|
148
184
|
/**
|
|
149
|
-
* Opens a TLS connection and extracts
|
|
150
|
-
*
|
|
151
|
-
* A permissive TrustManager is intentionally used to allow
|
|
152
|
-
* inspection of the certificate without enforcing trust validation.
|
|
185
|
+
* Opens a TLS connection and extracts
|
|
186
|
+
* the server leaf certificate.
|
|
153
187
|
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
188
|
+
* A permissive TrustManager is intentionally used
|
|
189
|
+
* to allow certificate inspection without enforcing
|
|
190
|
+
* system trust validation.
|
|
157
191
|
*/
|
|
192
|
+
@Throws(Exception::class)
|
|
158
193
|
private fun getCertificate(url: URL): Certificate {
|
|
159
194
|
val trustManagers =
|
|
160
195
|
arrayOf<TrustManager>(
|
|
161
196
|
object : X509TrustManager {
|
|
162
|
-
override fun getAcceptedIssuers() =
|
|
197
|
+
override fun getAcceptedIssuers() = emptyArray<java.security.cert.X509Certificate>()
|
|
163
198
|
|
|
164
199
|
override fun checkClientTrusted(
|
|
165
200
|
certs: Array<java.security.cert.X509Certificate>,
|
|
@@ -173,16 +208,28 @@ class SSLPinningImpl(
|
|
|
173
208
|
},
|
|
174
209
|
)
|
|
175
210
|
|
|
176
|
-
val sslContext =
|
|
177
|
-
|
|
211
|
+
val sslContext =
|
|
212
|
+
SSLContext.getInstance("TLS")
|
|
213
|
+
|
|
214
|
+
sslContext.init(
|
|
215
|
+
null,
|
|
216
|
+
trustManagers,
|
|
217
|
+
java.security.SecureRandom(),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
val connection =
|
|
221
|
+
url.openConnection() as HttpsURLConnection
|
|
222
|
+
|
|
223
|
+
connection.sslSocketFactory =
|
|
224
|
+
sslContext.socketFactory
|
|
178
225
|
|
|
179
|
-
val connection = url.openConnection() as HttpsURLConnection
|
|
180
|
-
connection.sslSocketFactory = sslContext.socketFactory
|
|
181
226
|
connection.connect()
|
|
182
227
|
|
|
183
|
-
val
|
|
228
|
+
val certificate =
|
|
229
|
+
connection.serverCertificates.first()
|
|
230
|
+
|
|
184
231
|
connection.disconnect()
|
|
185
232
|
|
|
186
|
-
return
|
|
233
|
+
return certificate
|
|
187
234
|
}
|
|
188
235
|
}
|
|
@@ -6,53 +6,140 @@ import com.getcapacitor.Plugin
|
|
|
6
6
|
import com.getcapacitor.PluginCall
|
|
7
7
|
import com.getcapacitor.PluginMethod
|
|
8
8
|
import com.getcapacitor.annotation.CapacitorPlugin
|
|
9
|
-
import io.capkit.sslpinning.utils.SSLPinningLogger
|
|
10
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Capacitor bridge for the SSLPinning plugin (Android).
|
|
12
|
+
*
|
|
13
|
+
* Responsibilities:
|
|
14
|
+
* - Parse JavaScript input
|
|
15
|
+
* - Call the native implementation
|
|
16
|
+
* - Resolve or reject PluginCall
|
|
17
|
+
* - Map native errors to JS-facing error codes
|
|
18
|
+
*/
|
|
11
19
|
@CapacitorPlugin(name = "SSLPinning")
|
|
12
20
|
class SSLPinningPlugin : Plugin() {
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Properties
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Immutable plugin configuration.
|
|
27
|
+
* Parsed once during plugin initialization.
|
|
28
|
+
*/
|
|
29
|
+
private lateinit var config: SSLPinningConfig
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Native implementation containing
|
|
33
|
+
* platform-specific logic only.
|
|
34
|
+
*/
|
|
13
35
|
private lateinit var implementation: SSLPinningImpl
|
|
14
36
|
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Lifecycle
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Called once when the plugin is loaded by the Capacitor bridge.
|
|
43
|
+
*
|
|
44
|
+
* This is the correct place to:
|
|
45
|
+
* - read static configuration
|
|
46
|
+
* - initialize native resources
|
|
47
|
+
* - inject configuration into the implementation
|
|
48
|
+
*/
|
|
15
49
|
override fun load() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
50
|
+
super.load()
|
|
51
|
+
|
|
52
|
+
config = SSLPinningConfig(this)
|
|
53
|
+
implementation = SSLPinningImpl(context)
|
|
54
|
+
implementation.updateConfig(config)
|
|
19
55
|
}
|
|
20
56
|
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Error Mapping
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Maps native SSLPinningError values
|
|
63
|
+
* to JavaScript-facing error codes.
|
|
64
|
+
*/
|
|
65
|
+
private fun reject(
|
|
66
|
+
call: PluginCall,
|
|
67
|
+
error: SSLPinningError,
|
|
68
|
+
) {
|
|
69
|
+
val code =
|
|
70
|
+
when (error) {
|
|
71
|
+
is SSLPinningError.Unavailable -> "UNAVAILABLE"
|
|
72
|
+
is SSLPinningError.PermissionDenied -> "PERMISSION_DENIED"
|
|
73
|
+
is SSLPinningError.InitFailed -> "INIT_FAILED"
|
|
74
|
+
is SSLPinningError.UnknownType -> "UNKNOWN_TYPE"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
call.reject(error.message, code)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// SSL Pinning (single fingerprint)
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validates the SSL certificate of a HTTPS endpoint
|
|
86
|
+
* using a single fingerprint.
|
|
87
|
+
*/
|
|
21
88
|
@PluginMethod
|
|
22
89
|
fun checkCertificate(call: PluginCall) {
|
|
23
|
-
val url = call.getString("url")
|
|
24
|
-
val fingerprint = call.getString("fingerprint")
|
|
25
|
-
|
|
26
|
-
if (url.
|
|
27
|
-
|
|
28
|
-
result.put("fingerprintMatched", false)
|
|
29
|
-
result.put("error", "Missing url")
|
|
30
|
-
call.resolve(result)
|
|
90
|
+
val url: String? = call.getString("url")
|
|
91
|
+
val fingerprint: String? = call.getString("fingerprint")
|
|
92
|
+
|
|
93
|
+
if (url.isNullOrBlank()) {
|
|
94
|
+
call.reject("Missing url", "UNKNOWN_TYPE")
|
|
31
95
|
return
|
|
32
96
|
}
|
|
33
97
|
|
|
34
98
|
execute {
|
|
35
|
-
|
|
36
|
-
val result =
|
|
37
|
-
|
|
38
|
-
|
|
99
|
+
try {
|
|
100
|
+
val result: Map<String, Any> =
|
|
101
|
+
implementation.checkCertificate(
|
|
102
|
+
urlString = url,
|
|
103
|
+
fingerprintFromArgs = fingerprint,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
val jsResult = JSObject()
|
|
107
|
+
for ((key, value) in result) {
|
|
108
|
+
jsResult.put(key, value)
|
|
39
109
|
}
|
|
40
|
-
|
|
110
|
+
|
|
111
|
+
call.resolve(jsResult)
|
|
112
|
+
} catch (error: SSLPinningError) {
|
|
113
|
+
reject(call, error)
|
|
114
|
+
} catch (error: Exception) {
|
|
115
|
+
call.reject(
|
|
116
|
+
error.message ?: "SSL pinning failed",
|
|
117
|
+
"INIT_FAILED",
|
|
118
|
+
)
|
|
41
119
|
}
|
|
42
120
|
}
|
|
43
121
|
}
|
|
44
122
|
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// SSL Pinning (multiple fingerprints)
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validates the SSL certificate of a HTTPS endpoint
|
|
129
|
+
* using multiple allowed fingerprints.
|
|
130
|
+
*/
|
|
45
131
|
@PluginMethod
|
|
46
132
|
fun checkCertificates(call: PluginCall) {
|
|
47
|
-
val url = call.getString("url")
|
|
133
|
+
val url: String? = call.getString("url")
|
|
48
134
|
|
|
49
135
|
val jsArray: JSArray? = call.getArray("fingerprints")
|
|
136
|
+
|
|
50
137
|
val fingerprints: List<String>? =
|
|
51
138
|
if (jsArray != null && jsArray.length() > 0) {
|
|
52
139
|
val list = ArrayList<String>()
|
|
53
140
|
for (i in 0 until jsArray.length()) {
|
|
54
141
|
val value = jsArray.getString(i)
|
|
55
|
-
if (!value.
|
|
142
|
+
if (!value.isNullOrBlank()) {
|
|
56
143
|
list.add(value)
|
|
57
144
|
}
|
|
58
145
|
}
|
|
@@ -61,22 +148,52 @@ class SSLPinningPlugin : Plugin() {
|
|
|
61
148
|
null
|
|
62
149
|
}
|
|
63
150
|
|
|
64
|
-
if (url.
|
|
65
|
-
|
|
66
|
-
result.put("fingerprintMatched", false)
|
|
67
|
-
result.put("error", "Missing url")
|
|
68
|
-
call.resolve(result)
|
|
151
|
+
if (url.isNullOrBlank()) {
|
|
152
|
+
call.reject("Missing url", "UNKNOWN_TYPE")
|
|
69
153
|
return
|
|
70
154
|
}
|
|
71
155
|
|
|
72
156
|
execute {
|
|
73
|
-
|
|
74
|
-
val result =
|
|
75
|
-
|
|
76
|
-
|
|
157
|
+
try {
|
|
158
|
+
val result: Map<String, Any> =
|
|
159
|
+
implementation.checkCertificates(
|
|
160
|
+
urlString = url,
|
|
161
|
+
fingerprintsFromArgs = fingerprints,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
val jsResult = JSObject()
|
|
165
|
+
for ((key, value) in result) {
|
|
166
|
+
jsResult.put(key, value)
|
|
77
167
|
}
|
|
78
|
-
|
|
168
|
+
|
|
169
|
+
call.resolve(jsResult)
|
|
170
|
+
} catch (error: SSLPinningError) {
|
|
171
|
+
reject(call, error)
|
|
172
|
+
} catch (error: Exception) {
|
|
173
|
+
call.reject(
|
|
174
|
+
error.message ?: "SSL pinning failed",
|
|
175
|
+
"INIT_FAILED",
|
|
176
|
+
)
|
|
79
177
|
}
|
|
80
178
|
}
|
|
81
179
|
}
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Version
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Returns the native plugin version.
|
|
187
|
+
*
|
|
188
|
+
* NOTE:
|
|
189
|
+
* - This method is guaranteed not to fail
|
|
190
|
+
* - Therefore it does NOT use TestError
|
|
191
|
+
* - Version is injected at build time from package.json
|
|
192
|
+
*/
|
|
193
|
+
@PluginMethod
|
|
194
|
+
fun getPluginVersion(call: PluginCall) {
|
|
195
|
+
val ret = JSObject()
|
|
196
|
+
ret.put("version", BuildConfig.PLUGIN_VERSION)
|
|
197
|
+
call.resolve(ret)
|
|
198
|
+
}
|
|
82
199
|
}
|