@cap-kit/tls-fingerprint
v8.0.1
Published
Runtime TLS leaf certificate SHA-256 fingerprint validation plugin for Capacitor (iOS & Android)
Readme
Overview
This Capacitor plugin validates the SHA-256 fingerprint of a server's TLS leaf certificate at runtime.
What this plugin does
- Extracts the leaf certificate from an HTTPS connection
- Computes its SHA-256 fingerprint
- Compares against expected fingerprints provided at runtime or in static configuration
What this plugin does NOT do
- It does NOT perform anchor-based certificate pinning
- It does NOT load local certificate files
- It does NOT modify or override the system trust store
- It does NOT validate the certificate chain
Platform Support
| Platform | Status |
| -------- | --------------------------------------------------- |
| iOS | Supported |
| Android | Supported |
| Web | Unsupported - methods reject with unimplemented() |
Install
pnpm add @cap-kit/tls-fingerprint
# or
npm install @cap-kit/tls-fingerprint
# or
yarn add @cap-kit/tls-fingerprint
# then run:
npx cap syncObtaining Fingerprints
To use this plugin, you need the SHA-256 fingerprint of the server certificate.
Method 1 — Using OpenSSL
openssl x509 -noout -fingerprint -sha256 -inform pem -in /path/to/cert.pemExample output:
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:62The plugin normalizes fingerprints to lowercase hex with no separators. For example,
EF:BA:26:...becomesefba26...
Method 2 — Using the Built-in CLI Tool
This project includes a CLI utility to retrieve certificates from remote servers:
npx cap-kit-tls-fingerprint example.comnpx cap-kit-tls-fingerprint example.com api.example.com --mode multiThe CLI is for development-time certificate inspection only. It does not perform runtime validation.
Configuration
Configuration options for the TLSFingerprint plugin.
| Prop | Type | Description | Default | Since |
| --------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
| verboseLogging | boolean | 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. | false | 8.0.0 |
| fingerprint | string | Default fingerprint used by checkCertificate() when options.fingerprint is not provided at runtime. | | 8.0.0 |
| fingerprints | string[] | Default fingerprints used by checkCertificates() when options.fingerprints is not provided at runtime. | | 8.0.0 |
| excludedDomains | string[] | Domains to bypass. Matches exact domain or subdomains. Do not include schemes or paths. | | 8.0.0 |
Examples
In capacitor.config.json:
{
"plugins": {
"TLSFingerprint": {
"verboseLogging": true,
"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",
"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"
]
}
}
}In capacitor.config.ts:
/// <reference types="@cap-kit/tls-fingerprint" />
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
plugins: {
TLSFingerprint: {
verboseLogging: true,
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',
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"],
},
},
};
export default config;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.
API
TLS Fingerprint Capacitor Plugin interface.
checkCertificate(...)
checkCertificate(options: TLSFingerprintOptions) => Promise<TLSFingerprintResult>Checks the SSL certificate of a server using a single fingerprint.
| Param | Type |
| ------------- | ----------------------------------------------------------------------- |
| options | TLSFingerprintOptions |
Returns: Promise<TLSFingerprintResult>
Since: 8.0.0
checkCertificates(...)
checkCertificates(options: TLSFingerprintMultiOptions) => Promise<TLSFingerprintResult>Checks the SSL certificate of a server using multiple allowed fingerprints.
| Param | Type |
| ------------- | --------------------------------------------------------------------------------- |
| options | TLSFingerprintMultiOptions |
Returns: Promise<TLSFingerprintResult>
Since: 8.0.0
getPluginVersion()
getPluginVersion() => Promise<PluginVersionResult>Returns the native plugin version.
The returned version corresponds to the native implementation bundled with the application.
Returns: Promise<PluginVersionResult>
Since: 8.0.0
Example
const { version } = await TLSFingerprint.getPluginVersion();Interfaces
TLSFingerprintResult
Result returned by an TLS fingerprint operation.
This object is returned for ALL outcomes:
- Success:
fingerprintMatched: true - Mismatch:
fingerprintMatched: falsewith error info (RESOLVED, not rejected)
Only operation failures (invalid input, config missing, network errors, timeout, internal errors) reject the Promise.
| Prop | Type | Description |
| ------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| actualFingerprint | string | The actual SHA-256 fingerprint of the server certificate. Present in fingerprint and excluded modes. |
| fingerprintMatched | boolean | Indicates whether the certificate validation succeeded. - true → Pinning passed - false → Pinning failed |
| matchedFingerprint | string | The fingerprint that successfully matched, if any. |
| excludedDomain | boolean | Indicates that TLS fingerprint was skipped because the request host matched an excluded domain. |
| mode | 'fingerprint' | 'excluded' | Indicates which pinning mode was used. - "fingerprint" - "excluded" |
| error | string | Human-readable error message when pinning fails. Present when fingerprintMatched: false. |
| errorCode | TLSFingerprintErrorCode | Standardized error code aligned with TLSFingerprintErrorCode. |
TLSFingerprintOptions
Options for checking a single SSL certificate.
| Prop | Type | Description |
| ----------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| url | string | HTTPS URL of the server whose SSL certificate must be checked. This value is REQUIRED and cannot be provided via configuration. |
| fingerprint | string | 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. |
TLSFingerprintMultiOptions
Options for checking an SSL certificate using multiple allowed fingerprints.
| Prop | Type | Description |
| ------------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| url | string | HTTPS URL of the server whose SSL certificate must be checked. This value is REQUIRED and cannot be provided via configuration. |
| fingerprints | string[] | 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. |
PluginVersionResult
Result returned by the getPluginVersion method.
| Prop | Type | Description |
| ------------- | ------------------- | ---------------------------------------- |
| version | string | The native version string of the plugin. |
Enums
TLSFingerprintErrorCode
| Members | Value | Description |
| ----------------------- | -------------------------------- | ------------------------------------------------------------------------------ |
| UNAVAILABLE | 'UNAVAILABLE' | Required data is missing or the feature is not available. |
| CANCELLED | 'CANCELLED' | The user cancelled an interactive flow. |
| PERMISSION_DENIED | 'PERMISSION_DENIED' | The user denied a required permission or the feature is disabled. |
| INIT_FAILED | 'INIT_FAILED' | The TLS fingerprint operation failed due to a runtime or initialization error. |
| INVALID_INPUT | 'INVALID_INPUT' | The input provided to the plugin method is invalid, missing, or malformed. |
| UNKNOWN_TYPE | 'UNKNOWN_TYPE' | Invalid or unsupported input was provided. |
| NOT_FOUND | 'NOT_FOUND' | The requested resource does not exist. |
| CONFLICT | 'CONFLICT' | The operation conflicts with the current state. |
| TIMEOUT | 'TIMEOUT' | The operation did not complete within the expected time. |
| PINNING_FAILED | 'PINNING_FAILED' | The server certificate fingerprint did not match any expected fingerprint. |
| EXCLUDED_DOMAIN | 'EXCLUDED_DOMAIN' | The request host matched an excluded domain. |
| NETWORK_ERROR | 'NETWORK_ERROR' | Network connectivity or TLS handshake error. |
| SSL_ERROR | 'SSL_ERROR' | SSL/TLS specific error (certificate expired, handshake failure, etc.). |
Security Considerations
This plugin validates fingerprint equality only.
What this means
- The plugin compares the server's leaf certificate SHA-256 fingerprint against expected values
- It does NOT replace TLS validation
- It does NOT override trust evaluation
- Expired or self-signed certificates will validate if the fingerprint matches
Limitations
- Fingerprint validation requires active maintenance
- Certificate rotation requires configuration updates
- Misconfiguration may result in loss of network connectivity
This plugin is provided as-is, without warranty. Always test thoroughly before production deployment.
Usage Examples
Single fingerprint check
import { TLSFingerprint } from '@cap-kit/tls-fingerprint';
const result = await TLSFingerprint.checkCertificate({
url: 'https://example.com',
fingerprint: 'aabbccdd...',
});
if (result.fingerprintMatched) {
console.log('Certificate is trusted');
} else {
console.log('Fingerprint mismatch:', result.error);
}Multiple fingerprints (certificate rotation)
import { TLSFingerprint } from '@cap-kit/tls-fingerprint';
const result = await TLSFingerprint.checkCertificates({
url: 'https://example.com',
fingerprints: ['aabbccdd...', '11223344...'],
});
if (result.fingerprintMatched) {
console.log('Certificate matched:', result.matchedFingerprint);
}Using static configuration
// capacitor.config.ts
plugins: {
TLSFingerprint: {
fingerprint: 'aabbccdd...',
excludedDomains: ['localhost', 'analytics.example.com']
}
}
// App code
const result = await TLSFingerprint.checkCertificate({
url: 'https://example.com',
});Contributing
Contributions are welcome. Please read the contributing guide before submitting a pull request.
Credits
This plugin is based on prior work from the community and has been refactored for Capacitor v8 and Swift Package Manager compatibility.
Original inspiration:
License
MIT
