@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,79 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
import CommonCrypto
|
|
4
|
+
|
|
5
|
+
struct TLSFingerprintUtils {
|
|
6
|
+
// MARK: - URL Helpers
|
|
7
|
+
|
|
8
|
+
static func httpsURL(from urlString: String) -> URL? {
|
|
9
|
+
guard let url = URL(string: urlString), url.scheme?.lowercased() == "https" else {
|
|
10
|
+
return nil
|
|
11
|
+
}
|
|
12
|
+
return url
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// MARK: - Fingerprint Normalization
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
Normalizes a fingerprint string by:
|
|
19
|
+
- Removing colon separators
|
|
20
|
+
- Removing all whitespace
|
|
21
|
+
- Converting to lowercase
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
"AA:BB:CC" → "aabbcc"
|
|
25
|
+
"AA BB CC" → "aabbcc"
|
|
26
|
+
*/
|
|
27
|
+
static func normalizeFingerprint(_ fingerprint: String) -> String {
|
|
28
|
+
fingerprint
|
|
29
|
+
.replacingOccurrences(of: ":", with: "")
|
|
30
|
+
.replacingOccurrences(of: " ", with: "")
|
|
31
|
+
.lowercased()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
Validates that a fingerprint string is a valid SHA-256 hex format.
|
|
36
|
+
|
|
37
|
+
Valid fingerprint:
|
|
38
|
+
- Exactly 64 hexadecimal characters (after normalization)
|
|
39
|
+
- Contains only [a-f0-9]
|
|
40
|
+
*/
|
|
41
|
+
static func isValidFingerprintFormat(_ fingerprint: String) -> Bool {
|
|
42
|
+
let normalized = normalizeFingerprint(fingerprint)
|
|
43
|
+
return normalized.count == 64 && normalized.allSatisfy { $0.isHexDigit }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
Validates a fingerprint and returns an error message if invalid, or nil if valid.
|
|
48
|
+
*/
|
|
49
|
+
static func validateFingerprint(_ fingerprint: String) -> String? {
|
|
50
|
+
let trimmed = fingerprint.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
51
|
+
if trimmed.isEmpty {
|
|
52
|
+
return "Fingerprint cannot be blank"
|
|
53
|
+
}
|
|
54
|
+
let normalized = normalizeFingerprint(fingerprint)
|
|
55
|
+
if normalized.count != 64 {
|
|
56
|
+
return "Invalid fingerprint: must be 64 hex characters"
|
|
57
|
+
}
|
|
58
|
+
if !normalized.allSatisfy({ $0.isHexDigit }) {
|
|
59
|
+
return "Invalid fingerprint: must contain only hex characters [a-f0-9]"
|
|
60
|
+
}
|
|
61
|
+
return nil
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// MARK: - Certificate Helpers
|
|
65
|
+
|
|
66
|
+
static func sha256Fingerprint(from certificate: SecCertificate) -> String {
|
|
67
|
+
let certData = SecCertificateCopyData(certificate) as Data
|
|
68
|
+
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
69
|
+
certData.withUnsafeBytes { bytes in
|
|
70
|
+
_ = CC_SHA256(bytes.baseAddress, CC_LONG(certData.count), &hash)
|
|
71
|
+
}
|
|
72
|
+
return hash.map { String(format: "%02x", $0) }.joined()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static func leafCertificate(from trust: SecTrust) -> SecCertificate? {
|
|
76
|
+
guard SecTrustGetCertificateCount(trust) > 0 else { return nil }
|
|
77
|
+
return SecTrustGetCertificateAtIndex(trust, 0)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import TLSFingerprint
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Basic functional tests for the TLSFingerprint plugin native implementation.
|
|
6
|
+
|
|
7
|
+
These tests validate the core behavior of the implementation
|
|
8
|
+
independently from the Capacitor bridge.
|
|
9
|
+
*/
|
|
10
|
+
class TLSFingerprintPluginTests: XCTestCase {
|
|
11
|
+
func testInstantiation() {
|
|
12
|
+
let implementation = TLSFingerprintImpl()
|
|
13
|
+
XCTAssertTrue(true)
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cap-kit/tls-fingerprint",
|
|
3
|
+
"version": "8.0.0",
|
|
4
|
+
"description": "Runtime TLS leaf certificate SHA-256 fingerprint validation plugin for Capacitor (iOS & Android)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=24.0.0",
|
|
10
|
+
"pnpm": ">=10.0.0"
|
|
11
|
+
},
|
|
12
|
+
"main": "dist/plugin.cjs",
|
|
13
|
+
"module": "dist/esm/index.js",
|
|
14
|
+
"types": "dist/esm/index.d.ts",
|
|
15
|
+
"unpkg": "dist/plugin.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/esm/index.d.ts",
|
|
19
|
+
"import": "./dist/esm/index.js",
|
|
20
|
+
"require": "./dist/plugin.cjs"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"android/src/main/",
|
|
26
|
+
"android/build.gradle",
|
|
27
|
+
"dist/",
|
|
28
|
+
"ios/Sources",
|
|
29
|
+
"ios/Tests",
|
|
30
|
+
"Package.swift",
|
|
31
|
+
"CapKitTlsFingerprint.podspec",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"README.md",
|
|
34
|
+
"bin/",
|
|
35
|
+
"scripts/"
|
|
36
|
+
],
|
|
37
|
+
"author": "CapKit Team",
|
|
38
|
+
"funding": {
|
|
39
|
+
"type": "github",
|
|
40
|
+
"url": "https://github.com/sponsors/cap-kit"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/cap-kit/capacitor-plugins.git",
|
|
46
|
+
"directory": "packages/tls-fingerprint"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/cap-kit/capacitor-plugins/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/cap-kit/capacitor-plugins/tree/main/packages/tls-fingerprint#readme",
|
|
52
|
+
"keywords": [
|
|
53
|
+
"capacitor",
|
|
54
|
+
"capacitor-plugin",
|
|
55
|
+
"mobile",
|
|
56
|
+
"native",
|
|
57
|
+
"cap-kit",
|
|
58
|
+
"ios",
|
|
59
|
+
"android",
|
|
60
|
+
"tls",
|
|
61
|
+
"https",
|
|
62
|
+
"certificate",
|
|
63
|
+
"certificate-fingerprint",
|
|
64
|
+
"tls-fingerprint",
|
|
65
|
+
"sha256",
|
|
66
|
+
"leaf-certificate",
|
|
67
|
+
"security"
|
|
68
|
+
],
|
|
69
|
+
"bin": {
|
|
70
|
+
"cap-kit-tls-fingerprint": "./dist/cli/fingerprint.js"
|
|
71
|
+
},
|
|
72
|
+
"publishConfig": {
|
|
73
|
+
"access": "public"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"yargs": "^18.0.0"
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@capacitor/android": "^8.1.0",
|
|
80
|
+
"@capacitor/cli": "^8.1.0",
|
|
81
|
+
"@capacitor/core": "^8.1.0",
|
|
82
|
+
"@capacitor/docgen": "^0.3.1",
|
|
83
|
+
"@capacitor/ios": "^8.1.0",
|
|
84
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
85
|
+
"@eslint/js": "^9.39.2",
|
|
86
|
+
"eslint": "^9.39.2",
|
|
87
|
+
"eslint-plugin-import": "^2.32.0",
|
|
88
|
+
"@types/node": "^25.3.0",
|
|
89
|
+
"@types/yargs": "^17.0.35",
|
|
90
|
+
"globals": "^17.3.0",
|
|
91
|
+
"prettier": "^3.8.1",
|
|
92
|
+
"prettier-plugin-java": "^2.8.1",
|
|
93
|
+
"rimraf": "^6.1.3",
|
|
94
|
+
"rollup": "^4.57.1",
|
|
95
|
+
"swiftlint": "^2.0.0",
|
|
96
|
+
"typescript": "~5.9.3",
|
|
97
|
+
"typescript-eslint": "^8.56.0"
|
|
98
|
+
},
|
|
99
|
+
"peerDependencies": {
|
|
100
|
+
"@capacitor/core": "^8.1.0"
|
|
101
|
+
},
|
|
102
|
+
"capacitor": {
|
|
103
|
+
"ios": {
|
|
104
|
+
"src": "ios"
|
|
105
|
+
},
|
|
106
|
+
"android": {
|
|
107
|
+
"src": "android"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"scripts": {
|
|
111
|
+
"verify": "pnpm run verify:ios && pnpm run verify:android && pnpm run verify:web",
|
|
112
|
+
"verify:ios": "node ./scripts/sync-version.mjs && xcodebuild -scheme CapKitTlsFingerprint -destination generic/platform=iOS",
|
|
113
|
+
"verify:android": "cd android && ./gradlew clean build && cd ..",
|
|
114
|
+
"verify:web": "pnpm run build",
|
|
115
|
+
"lint:android": "cd android && ./gradlew ktlintCheck",
|
|
116
|
+
"fmt:android": "cd android && ./gradlew ktlintFormat",
|
|
117
|
+
"lint": "pnpm run eslint . && pnpm run swiftlint lint --strict || true && pnpm run lint:android || true",
|
|
118
|
+
"format:check": "prettier --check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
119
|
+
"format": "eslint --fix . && prettier --write \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java && pnpm run swiftlint --fix --format && pnpm run fmt:android",
|
|
120
|
+
"eslint": "eslint",
|
|
121
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
122
|
+
"swiftlint": "node-swiftlint lint ios/Sources",
|
|
123
|
+
"docgen": "docgen --api TLSFingerprintPlugin --output-readme README.md --output-json dist/docs.json",
|
|
124
|
+
"build": "node ./scripts/sync-version.mjs && pnpm run clean && pnpm run docgen && tsc && rollup -c rollup.config.mjs && node scripts/chmod.mjs",
|
|
125
|
+
"clean": "rimraf ./dist",
|
|
126
|
+
"watch": "tsc --watch",
|
|
127
|
+
"test": "pnpm run verify",
|
|
128
|
+
"removePacked": "rimraf -g cap-kit-tls-fingerprint-*.tgz",
|
|
129
|
+
"publish:locally": "pnpm run removePacked && pnpm run build && pnpm pack"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
// Reconstruct __dirname, which does not exist in ESM
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// Correct path to the compiled CLI
|
|
10
|
+
const cliPath = path.resolve(__dirname, '../dist/cli/fingerprint.js');
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
if (fs.existsSync(cliPath)) {
|
|
14
|
+
// 1. Read the file contents
|
|
15
|
+
let content = fs.readFileSync(cliPath, 'utf8');
|
|
16
|
+
|
|
17
|
+
// 2. Add shebang if missing
|
|
18
|
+
if (!content.startsWith('#!/usr/bin/env node')) {
|
|
19
|
+
content = '#!/usr/bin/env node\n' + content;
|
|
20
|
+
fs.writeFileSync(cliPath, content);
|
|
21
|
+
console.log('✅ Shebang added to CLI.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 3. Make the file executable
|
|
25
|
+
fs.chmodSync(cliPath, '755');
|
|
26
|
+
console.log('✅ CLI permissions set to 755.');
|
|
27
|
+
} else {
|
|
28
|
+
console.error(`❌ CLI file not found at: ${cliPath}`);
|
|
29
|
+
// Do not fail the build if the file does not exist yet, just warn
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error('❌ Error setting permissions:', err);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
// 1. Setup __dirname equivalent for ES Modules
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// 2. Load the plugin's package.json
|
|
10
|
+
const pkgPath = path.join(__dirname, '../package.json');
|
|
11
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
12
|
+
|
|
13
|
+
// 3. Automatically find the iOS source directory
|
|
14
|
+
// Capacitor standard structure: ios/Sources/<PluginName>
|
|
15
|
+
const sourcesDir = path.join(__dirname, '../ios/Sources');
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(sourcesDir)) {
|
|
18
|
+
console.warn('⚠️ [CapKit] ios/Sources directory not found. Skipping Version.swift generation.');
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pluginFolderName = fs.readdirSync(sourcesDir).find((f) => fs.statSync(path.join(sourcesDir, f)).isDirectory());
|
|
23
|
+
|
|
24
|
+
if (!pluginFolderName) {
|
|
25
|
+
console.error('❌ [CapKit] Could not find a source directory in ios/Sources');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const versionFilePath = path.join(sourcesDir, pluginFolderName, 'Version.swift');
|
|
30
|
+
const webVersionFilePath = path.join(__dirname, '../src/version.ts');
|
|
31
|
+
|
|
32
|
+
const content = `// This file is automatically generated. Do not modify manually.
|
|
33
|
+
import Foundation
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
Container for the plugin's version information.
|
|
37
|
+
This enum provides a centralized, single source of truth for the native
|
|
38
|
+
version string, synchronized directly from the project's 'package.json'.
|
|
39
|
+
It ensures parity across JavaScript, Android, and iOS platforms.
|
|
40
|
+
*/
|
|
41
|
+
public enum PluginVersion {
|
|
42
|
+
/**
|
|
43
|
+
The semantic version string of the plugin.
|
|
44
|
+
Value synchronized from package.json: "${pkg.version}"
|
|
45
|
+
*/
|
|
46
|
+
public static let number = "${pkg.version}"
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
fs.writeFileSync(versionFilePath, content);
|
|
52
|
+
console.log(`✅ [CapKit] Version.swift updated to v${pkg.version} in ${pluginFolderName}`);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('❌ [CapKit] Error generating Version.swift:', err);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const webVersionContent = `// This file is automatically generated. Do not modify manually.
|
|
59
|
+
export const PLUGIN_VERSION = '${pkg.version}';
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
fs.writeFileSync(webVersionFilePath, webVersionContent);
|
|
64
|
+
console.log(`✅ [CapKit] src/version.ts updated to v${pkg.version}`);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error('❌ [CapKit] Error generating src/version.ts:', err);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|