@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,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapKitTlsFingerprint'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '15.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.9'
|
|
17
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CapKit Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Package.swift
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapKitTlsFingerprint",
|
|
6
|
+
platforms: [.iOS(.v15)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapKitTlsFingerprint",
|
|
10
|
+
targets: ["TlsFingerprintPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.1.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "TlsFingerprintPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/TLSFingerprintPlugin"
|
|
23
|
+
)
|
|
24
|
+
]
|
|
25
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img
|
|
3
|
+
src="https://raw.githubusercontent.com/cap-kit/capacitor-plugins/main/assets/logo.png"
|
|
4
|
+
alt="CapKit Logo"
|
|
5
|
+
width="128"
|
|
6
|
+
/>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<h3 align="center">TLS Fingerprinting</h3>
|
|
10
|
+
<p align="center">
|
|
11
|
+
<strong>
|
|
12
|
+
<code>@cap-kit/tls-fingerprint</code>
|
|
13
|
+
</strong>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
Runtime TLS leaf certificate SHA-256 fingerprint validation for Capacitor applications. This plugin establishes a TLS
|
|
18
|
+
connection to a remote HTTPS endpoint, extracts the server’s leaf certificate, computes its SHA-256 fingerprint, and
|
|
19
|
+
compares it against one or more expected fingerprints defined at runtime or via static configuration. It performs
|
|
20
|
+
fingerprint equality validation only and does not override or modify the system trust store.
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<a href="https://www.npmjs.com/package/@cap-kit/tls-fingerprint">
|
|
25
|
+
<img src="https://img.shields.io/npm/v/@cap-kit/tls-fingerprint?color=blue&label=npm&logo=npm&style=flat-square" alt="npm version">
|
|
26
|
+
</a>
|
|
27
|
+
<a href="https://github.com/cap-kit/capacitor-plugins/actions">
|
|
28
|
+
<img src="https://img.shields.io/github/actions/workflow/status/cap-kit/capacitor-plugins/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" />
|
|
29
|
+
</a>
|
|
30
|
+
<a href="https://capacitorjs.com/">
|
|
31
|
+
<img src="https://img.shields.io/badge/Capacitor-Plugin-blue?logo=capacitor&style=flat-square" alt="Capacitor Plugin">
|
|
32
|
+
</a>
|
|
33
|
+
<a href="https://www.npmjs.com/package/@cap-kit/tls-fingerprint">
|
|
34
|
+
<img src="https://img.shields.io/npm/dm/@cap-kit/tls-fingerprint?style=flat-square" alt="Downloads" />
|
|
35
|
+
</a>
|
|
36
|
+
<a href="./LICENSE">
|
|
37
|
+
<img src="https://img.shields.io/npm/l/@cap-kit/tls-fingerprint?style=flat-square&logo=open-source-initiative&logoColor=white&color=green" alt="License" />
|
|
38
|
+
</a>
|
|
39
|
+
<img src="https://img.shields.io/maintenance/yes/2026?style=flat-square" alt="Maintained" />
|
|
40
|
+
</p>
|
|
41
|
+
<br>
|
|
42
|
+
|
|
43
|
+
## Overview
|
|
44
|
+
|
|
45
|
+
This Capacitor plugin validates the SHA-256 fingerprint of a server's TLS leaf certificate at runtime.
|
|
46
|
+
|
|
47
|
+
### What this plugin does
|
|
48
|
+
|
|
49
|
+
- Extracts the leaf certificate from an HTTPS connection
|
|
50
|
+
- Computes its SHA-256 fingerprint
|
|
51
|
+
- Compares against expected fingerprints provided at runtime or in static configuration
|
|
52
|
+
|
|
53
|
+
### What this plugin does NOT do
|
|
54
|
+
|
|
55
|
+
- It does NOT perform anchor-based certificate pinning
|
|
56
|
+
- It does NOT load local certificate files
|
|
57
|
+
- It does NOT modify or override the system trust store
|
|
58
|
+
- It does NOT validate the certificate chain
|
|
59
|
+
|
|
60
|
+
### Platform Support
|
|
61
|
+
|
|
62
|
+
| Platform | Status |
|
|
63
|
+
| -------- | --------------------------------------------------- |
|
|
64
|
+
| iOS | Supported |
|
|
65
|
+
| Android | Supported |
|
|
66
|
+
| Web | Unsupported - methods reject with `unimplemented()` |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pnpm add @cap-kit/tls-fingerprint
|
|
74
|
+
# or
|
|
75
|
+
npm install @cap-kit/tls-fingerprint
|
|
76
|
+
# or
|
|
77
|
+
yarn add @cap-kit/tls-fingerprint
|
|
78
|
+
# then run:
|
|
79
|
+
npx cap sync
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Obtaining Fingerprints
|
|
85
|
+
|
|
86
|
+
To use this plugin, you need the SHA-256 fingerprint of the server certificate.
|
|
87
|
+
|
|
88
|
+
### Method 1 — Using OpenSSL
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
openssl x509 -noout -fingerprint -sha256 -inform pem -in /path/to/cert.pem
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Example output:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
SHA256 Fingerprint=EF:BA:26:D8:C1:CE:37:79:AC:77:63:0A:90:F8:21:63:A3:D6:89:2E:D6:AF:EE:40:86:72:CF:19:EB:A7:A3:62
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> The plugin normalizes fingerprints to lowercase hex with no separators.
|
|
101
|
+
> For example, `EF:BA:26:...` becomes `efba26...`
|
|
102
|
+
|
|
103
|
+
### Method 2 — Using the Built-in CLI Tool
|
|
104
|
+
|
|
105
|
+
This project includes a CLI utility to retrieve certificates from remote servers:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx cap-kit-tls-fingerprint example.com
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx cap-kit-tls-fingerprint example.com api.example.com --mode multi
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The CLI is for development-time certificate inspection only. It does not perform runtime validation.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Configuration
|
|
120
|
+
|
|
121
|
+
<docgen-config>
|
|
122
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
123
|
+
|
|
124
|
+
Configuration options for the TLSFingerprint plugin.
|
|
125
|
+
|
|
126
|
+
| Prop | Type | Description | Default | Since |
|
|
127
|
+
| --------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
|
|
128
|
+
| **`verboseLogging`** | <code>boolean</code> | Enables verbose native logging. When enabled, additional debug information is printed to the native console (Logcat on Android, Xcode on iOS). This option affects native logging behavior only and has no impact on the JavaScript API. | <code>false</code> | 8.0.0 |
|
|
129
|
+
| **`fingerprint`** | <code>string</code> | Default fingerprint used by `checkCertificate()` when `options.fingerprint` is not provided at runtime. | | 8.0.0 |
|
|
130
|
+
| **`fingerprints`** | <code>string[]</code> | Default fingerprints used by `checkCertificates()` when `options.fingerprints` is not provided at runtime. | | 8.0.0 |
|
|
131
|
+
| **`excludedDomains`** | <code>string[]</code> | Domains to bypass. Matches exact domain or subdomains. Do not include schemes or paths. | | 8.0.0 |
|
|
132
|
+
|
|
133
|
+
### Examples
|
|
134
|
+
|
|
135
|
+
In `capacitor.config.json`:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"plugins": {
|
|
140
|
+
"TLSFingerprint": {
|
|
141
|
+
"verboseLogging": true,
|
|
142
|
+
"fingerprint": "50:4B:A1:B5:48:96:71:F3:9F:87:7E:0A:09:FD:3E:1B:C0:4F:AA:9F:FC:83:3E:A9:3A:00:78:88:F8:BA:60:26",
|
|
143
|
+
"fingerprints": [
|
|
144
|
+
"50:4B:A1:B5:48:96:71:F3:9F:87:7E:0A:09:FD:3E:1B:C0:4F:AA:9F:FC:83:3E:A9:3A:00:78:88:F8:BA:60:26"
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
In `capacitor.config.ts`:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
/// <reference types="@cap-kit/tls-fingerprint" />
|
|
155
|
+
|
|
156
|
+
import { CapacitorConfig } from '@capacitor/cli';
|
|
157
|
+
|
|
158
|
+
const config: CapacitorConfig = {
|
|
159
|
+
plugins: {
|
|
160
|
+
TLSFingerprint: {
|
|
161
|
+
verboseLogging: true,
|
|
162
|
+
fingerprint: '50:4B:A1:B5:48:96:71:F3:9F:87:7E:0A:09:FD:3E:1B:C0:4F:AA:9F:FC:83:3E:A9:3A:00:78:88:F8:BA:60:26',
|
|
163
|
+
fingerprints: ["50:4B:A1:B5:48:96:71:F3:9F:87:7E:0A:09:FD:3E:1B:C0:4F:AA:9F:FC:83:3E:A9:3A:00:78:88:F8:BA:60:26"],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export default config;
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
</docgen-config>
|
|
172
|
+
|
|
173
|
+
> **Note:** All network operations have a 10-second timeout. If the server does not respond within this time, the Promise is rejected with `TLSFingerprintErrorCode.TIMEOUT`.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## API
|
|
178
|
+
|
|
179
|
+
<docgen-index>
|
|
180
|
+
|
|
181
|
+
* [`checkCertificate(...)`](#checkcertificate)
|
|
182
|
+
* [`checkCertificates(...)`](#checkcertificates)
|
|
183
|
+
* [`getPluginVersion()`](#getpluginversion)
|
|
184
|
+
* [Interfaces](#interfaces)
|
|
185
|
+
* [Enums](#enums)
|
|
186
|
+
|
|
187
|
+
</docgen-index>
|
|
188
|
+
|
|
189
|
+
<docgen-api>
|
|
190
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
191
|
+
|
|
192
|
+
TLS Fingerprint Capacitor Plugin interface.
|
|
193
|
+
|
|
194
|
+
### checkCertificate(...)
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
checkCertificate(options: TLSFingerprintOptions) => Promise<TLSFingerprintResult>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Checks the SSL certificate of a server using a single fingerprint.
|
|
201
|
+
|
|
202
|
+
| Param | Type |
|
|
203
|
+
| ------------- | ----------------------------------------------------------------------- |
|
|
204
|
+
| **`options`** | <code><a href="#tlsfingerprintoptions">TLSFingerprintOptions</a></code> |
|
|
205
|
+
|
|
206
|
+
**Returns:** <code>Promise<<a href="#tlsfingerprintresult">TLSFingerprintResult</a>></code>
|
|
207
|
+
|
|
208
|
+
**Since:** 8.0.0
|
|
209
|
+
|
|
210
|
+
--------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
### checkCertificates(...)
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
checkCertificates(options: TLSFingerprintMultiOptions) => Promise<TLSFingerprintResult>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Checks the SSL certificate of a server using multiple allowed fingerprints.
|
|
220
|
+
|
|
221
|
+
| Param | Type |
|
|
222
|
+
| ------------- | --------------------------------------------------------------------------------- |
|
|
223
|
+
| **`options`** | <code><a href="#tlsfingerprintmultioptions">TLSFingerprintMultiOptions</a></code> |
|
|
224
|
+
|
|
225
|
+
**Returns:** <code>Promise<<a href="#tlsfingerprintresult">TLSFingerprintResult</a>></code>
|
|
226
|
+
|
|
227
|
+
**Since:** 8.0.0
|
|
228
|
+
|
|
229
|
+
--------------------
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
### getPluginVersion()
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
getPluginVersion() => Promise<PluginVersionResult>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Returns the native plugin version.
|
|
239
|
+
|
|
240
|
+
The returned version corresponds to the native implementation
|
|
241
|
+
bundled with the application.
|
|
242
|
+
|
|
243
|
+
**Returns:** <code>Promise<<a href="#pluginversionresult">PluginVersionResult</a>></code>
|
|
244
|
+
|
|
245
|
+
**Since:** 8.0.0
|
|
246
|
+
|
|
247
|
+
#### Example
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const { version } = await TLSFingerprint.getPluginVersion();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
--------------------
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
### Interfaces
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
#### TLSFingerprintResult
|
|
260
|
+
|
|
261
|
+
Result returned by an TLS fingerprint operation.
|
|
262
|
+
|
|
263
|
+
This object is returned for ALL outcomes:
|
|
264
|
+
- Success: `fingerprintMatched: true`
|
|
265
|
+
- Mismatch: `fingerprintMatched: false` with error info (RESOLVED, not rejected)
|
|
266
|
+
|
|
267
|
+
Only operation failures (invalid input, config missing, network errors,
|
|
268
|
+
timeout, internal errors) reject the Promise.
|
|
269
|
+
|
|
270
|
+
| Prop | Type | Description |
|
|
271
|
+
| ------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
272
|
+
| **`actualFingerprint`** | <code>string</code> | The actual SHA-256 fingerprint of the server certificate. Present in fingerprint and excluded modes. |
|
|
273
|
+
| **`fingerprintMatched`** | <code>boolean</code> | Indicates whether the certificate validation succeeded. - true → Pinning passed - false → Pinning failed |
|
|
274
|
+
| **`matchedFingerprint`** | <code>string</code> | The fingerprint that successfully matched, if any. |
|
|
275
|
+
| **`excludedDomain`** | <code>boolean</code> | Indicates that TLS fingerprint was skipped because the request host matched an excluded domain. |
|
|
276
|
+
| **`mode`** | <code>'fingerprint' \| 'excluded'</code> | Indicates which pinning mode was used. - "fingerprint" - "excluded" |
|
|
277
|
+
| **`error`** | <code>string</code> | Human-readable error message when pinning fails. Present when `fingerprintMatched: false`. |
|
|
278
|
+
| **`errorCode`** | <code><a href="#tlsfingerprinterrorcode">TLSFingerprintErrorCode</a></code> | Standardized error code aligned with <a href="#tlsfingerprinterrorcode">TLSFingerprintErrorCode</a>. |
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
#### TLSFingerprintOptions
|
|
282
|
+
|
|
283
|
+
Options for checking a single SSL certificate.
|
|
284
|
+
|
|
285
|
+
| Prop | Type | Description |
|
|
286
|
+
| ----------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
287
|
+
| **`url`** | <code>string</code> | HTTPS URL of the server whose SSL certificate must be checked. This value is REQUIRED and cannot be provided via configuration. |
|
|
288
|
+
| **`fingerprint`** | <code>string</code> | Expected SHA-256 fingerprint of the certificate. Resolution order: 1. `options.fingerprint` (runtime) 2. `plugins.TLSFingerprint.fingerprint` (config) If neither is provided, the Promise is rejected with `TLSFingerprintErrorCode.UNAVAILABLE`. |
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
#### TLSFingerprintMultiOptions
|
|
292
|
+
|
|
293
|
+
Options for checking an SSL certificate using multiple allowed fingerprints.
|
|
294
|
+
|
|
295
|
+
| Prop | Type | Description |
|
|
296
|
+
| ------------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
297
|
+
| **`url`** | <code>string</code> | HTTPS URL of the server whose SSL certificate must be checked. This value is REQUIRED and cannot be provided via configuration. |
|
|
298
|
+
| **`fingerprints`** | <code>string[]</code> | Expected SHA-256 fingerprints of the certificate. Resolution order: 1. `options.fingerprints` (runtime) 2. `plugins.TLSFingerprint.fingerprints` (config) If neither is provided, the Promise is rejected with `TLSFingerprintErrorCode.UNAVAILABLE`. |
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
#### PluginVersionResult
|
|
302
|
+
|
|
303
|
+
Result returned by the getPluginVersion method.
|
|
304
|
+
|
|
305
|
+
| Prop | Type | Description |
|
|
306
|
+
| ------------- | ------------------- | ---------------------------------------- |
|
|
307
|
+
| **`version`** | <code>string</code> | The native version string of the plugin. |
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
### Enums
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
#### TLSFingerprintErrorCode
|
|
314
|
+
|
|
315
|
+
| Members | Value | Description |
|
|
316
|
+
| ----------------------- | -------------------------------- | ------------------------------------------------------------------------------ |
|
|
317
|
+
| **`UNAVAILABLE`** | <code>'UNAVAILABLE'</code> | Required data is missing or the feature is not available. |
|
|
318
|
+
| **`CANCELLED`** | <code>'CANCELLED'</code> | The user cancelled an interactive flow. |
|
|
319
|
+
| **`PERMISSION_DENIED`** | <code>'PERMISSION_DENIED'</code> | The user denied a required permission or the feature is disabled. |
|
|
320
|
+
| **`INIT_FAILED`** | <code>'INIT_FAILED'</code> | The TLS fingerprint operation failed due to a runtime or initialization error. |
|
|
321
|
+
| **`INVALID_INPUT`** | <code>'INVALID_INPUT'</code> | The input provided to the plugin method is invalid, missing, or malformed. |
|
|
322
|
+
| **`UNKNOWN_TYPE`** | <code>'UNKNOWN_TYPE'</code> | Invalid or unsupported input was provided. |
|
|
323
|
+
| **`NOT_FOUND`** | <code>'NOT_FOUND'</code> | The requested resource does not exist. |
|
|
324
|
+
| **`CONFLICT`** | <code>'CONFLICT'</code> | The operation conflicts with the current state. |
|
|
325
|
+
| **`TIMEOUT`** | <code>'TIMEOUT'</code> | The operation did not complete within the expected time. |
|
|
326
|
+
| **`PINNING_FAILED`** | <code>'PINNING_FAILED'</code> | The server certificate fingerprint did not match any expected fingerprint. |
|
|
327
|
+
| **`EXCLUDED_DOMAIN`** | <code>'EXCLUDED_DOMAIN'</code> | The request host matched an excluded domain. |
|
|
328
|
+
| **`NETWORK_ERROR`** | <code>'NETWORK_ERROR'</code> | Network connectivity or TLS handshake error. |
|
|
329
|
+
| **`SSL_ERROR`** | <code>'SSL_ERROR'</code> | SSL/TLS specific error (certificate expired, handshake failure, etc.). |
|
|
330
|
+
|
|
331
|
+
</docgen-api>
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Security Considerations
|
|
336
|
+
|
|
337
|
+
This plugin validates fingerprint equality only.
|
|
338
|
+
|
|
339
|
+
### What this means
|
|
340
|
+
|
|
341
|
+
- The plugin compares the server's leaf certificate SHA-256 fingerprint against expected values
|
|
342
|
+
- It does NOT replace TLS validation
|
|
343
|
+
- It does NOT override trust evaluation
|
|
344
|
+
- Expired or self-signed certificates will validate if the fingerprint matches
|
|
345
|
+
|
|
346
|
+
### Limitations
|
|
347
|
+
|
|
348
|
+
- Fingerprint validation requires active maintenance
|
|
349
|
+
- Certificate rotation requires configuration updates
|
|
350
|
+
- Misconfiguration may result in loss of network connectivity
|
|
351
|
+
|
|
352
|
+
This plugin is provided as-is, without warranty. Always test thoroughly before production deployment.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Usage Examples
|
|
357
|
+
|
|
358
|
+
### Single fingerprint check
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { TLSFingerprint } from '@cap-kit/tls-fingerprint';
|
|
362
|
+
|
|
363
|
+
const result = await TLSFingerprint.checkCertificate({
|
|
364
|
+
url: 'https://example.com',
|
|
365
|
+
fingerprint: 'aabbccdd...',
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (result.fingerprintMatched) {
|
|
369
|
+
console.log('Certificate is trusted');
|
|
370
|
+
} else {
|
|
371
|
+
console.log('Fingerprint mismatch:', result.error);
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Multiple fingerprints (certificate rotation)
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
import { TLSFingerprint } from '@cap-kit/tls-fingerprint';
|
|
379
|
+
|
|
380
|
+
const result = await TLSFingerprint.checkCertificates({
|
|
381
|
+
url: 'https://example.com',
|
|
382
|
+
fingerprints: ['aabbccdd...', '11223344...'],
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
if (result.fingerprintMatched) {
|
|
386
|
+
console.log('Certificate matched:', result.matchedFingerprint);
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Using static configuration
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
// capacitor.config.ts
|
|
394
|
+
plugins: {
|
|
395
|
+
TLSFingerprint: {
|
|
396
|
+
fingerprint: 'aabbccdd...',
|
|
397
|
+
excludedDomains: ['localhost', 'analytics.example.com']
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// App code
|
|
402
|
+
const result = await TLSFingerprint.checkCertificate({
|
|
403
|
+
url: 'https://example.com',
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Contributing
|
|
410
|
+
|
|
411
|
+
Contributions are welcome. Please read the [contributing guide](CONTRIBUTING.md) before submitting a pull request.
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Credits
|
|
416
|
+
|
|
417
|
+
This plugin is based on prior work from the community and has been refactored for Capacitor v8 and Swift Package Manager compatibility.
|
|
418
|
+
|
|
419
|
+
Original inspiration:
|
|
420
|
+
|
|
421
|
+
- [https://github.com/mchl18/Capacitor-SSL-Pinning](https://github.com/mchl18/Capacitor-SSL-Pinning)
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## License
|
|
426
|
+
|
|
427
|
+
MIT
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '2.2.20'
|
|
3
|
+
repositories {
|
|
4
|
+
google()
|
|
5
|
+
mavenCentral()
|
|
6
|
+
}
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath 'com.android.tools.build:gradle:8.13.2'
|
|
9
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
plugins {
|
|
14
|
+
id "org.jlleitschuh.gradle.ktlint" version "14.0.1" apply false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
ext {
|
|
18
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
19
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
20
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
21
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
22
|
+
androidxCoreKTXVersion = project.hasProperty('androidxCoreKTXVersion') ? rootProject.ext.androidxCoreKTXVersion : '1.17.0'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
apply plugin: 'com.android.library'
|
|
26
|
+
apply plugin: 'kotlin-android'
|
|
27
|
+
apply plugin: 'kotlin-parcelize'
|
|
28
|
+
apply plugin: 'org.jlleitschuh.gradle.ktlint'
|
|
29
|
+
|
|
30
|
+
import groovy.json.JsonSlurper
|
|
31
|
+
|
|
32
|
+
def getPluginVersion() {
|
|
33
|
+
try {
|
|
34
|
+
def packageJsonFile = file('../package.json')
|
|
35
|
+
if (packageJsonFile.exists()) {
|
|
36
|
+
def packageJson = new JsonSlurper().parseText(packageJsonFile.text)
|
|
37
|
+
if (packageJson.version) {
|
|
38
|
+
return packageJson.version
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch (Exception e) {
|
|
42
|
+
throw new GradleException("Failed to read plugin version from package.json", e)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new GradleException("Plugin version not found in ../package.json")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def pluginVersion = getPluginVersion()
|
|
49
|
+
|
|
50
|
+
android {
|
|
51
|
+
namespace = "io.capkit.tlsfingerprint"
|
|
52
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion as Integer : 36
|
|
53
|
+
|
|
54
|
+
// AGP 8.0+ disables BuildConfig by default for libraries.
|
|
55
|
+
// We need to enable it to inject the plugin version.
|
|
56
|
+
buildFeatures {
|
|
57
|
+
buildConfig = true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
defaultConfig {
|
|
61
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion as Integer : 24
|
|
62
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion as Integer : 36
|
|
63
|
+
versionCode 1
|
|
64
|
+
|
|
65
|
+
// Dynamic versioning (feature enabled)
|
|
66
|
+
versionName = pluginVersion
|
|
67
|
+
|
|
68
|
+
// Injects the version into the BuildConfig class ONLY if feature is enabled
|
|
69
|
+
buildConfigField "String", "PLUGIN_VERSION", "\"${pluginVersion}\""
|
|
70
|
+
|
|
71
|
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
72
|
+
}
|
|
73
|
+
buildTypes {
|
|
74
|
+
release {
|
|
75
|
+
minifyEnabled = false
|
|
76
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
lint {
|
|
80
|
+
abortOnError = false
|
|
81
|
+
}
|
|
82
|
+
compileOptions {
|
|
83
|
+
sourceCompatibility = JavaVersion.VERSION_21
|
|
84
|
+
targetCompatibility = JavaVersion.VERSION_21
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
repositories {
|
|
89
|
+
google()
|
|
90
|
+
mavenCentral()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
dependencies {
|
|
94
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
95
|
+
implementation project(':capacitor-android')
|
|
96
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
97
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
98
|
+
implementation "androidx.core:core-ktx:$androidxCoreKTXVersion"
|
|
99
|
+
|
|
100
|
+
testImplementation "junit:junit:$junitVersion"
|
|
101
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
102
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
103
|
+
}
|