smart_dev_pinning_plugin 3.1.0
smart_dev_pinning_plugin: ^3.1.0 copied to clipboard
This plugin creates a secure native TLS connection to execute HTTP requests with certificate pinning.
Smart Dev Pinning Plugin #
A high-performance Flutter plugin that provides secure native TLS connections for HTTP requests with advanced SSL certificate pinning capabilities. Supports Leaf and Intermediate CA pinning in both public key and certificate hash modes, with non-blocking asynchronous FFI implementation.
Features #
- 🔐 4 Pinning Methods: Leaf Certificate, Leaf Public Key, Intermediate Certificate, Intermediate Public Key
- 🚀 High Performance: Native FFI implementation with asynchronous execution.
- 🎯 Non-blocking UI: Isolate-based FFI execution prevents UI freezing on Android
- 📊 Built-in Benchmarking: Comprehensive performance analysis tools
- 🛡️ Type-safe API: Enum-based pinning method selection for enhanced safety
- 📱 Cross-platform: Full Android and iOS support
- ⚡ Zero Dependencies: Minimal external dependencies for maximum compatibility
Key Improvements #
- Asynchronous FFI: To execute FFI operations in separate Isolates
- Enhanced Performance: Non-blocking operations ensure responsive UI during network requests
- Consistent Behavior: Uniform asynchronous behavior across Android and iOS platforms
- Modern Architecture: Improved native integration with (Android) and Swift (iOS)
Requirements #
- Flutter 3.3.0 or higher
- Dart SDK 3.3.0 or higher
- Android API level 21+ (Android 5.0+)
- iOS 11.0+
Installation #
Add the dependency to your pubspec.yaml:
dependencies:
smart_dev_pinning_plugin: ^2.6.0
Then run:
flutter pub get
Usage Guide #
SSL Pinning Methods #
This plugin provides four certificate pinning strategies:
Leaf Pinning
-
Public Key Pinning (
PinningMethod.publicKey):- Pins against the server's (Leaf) public key
- Survives certificate renewals if the key is reused
- Recommended for services you control
-
Leaf Certificate Pinning (
PinningMethod.certificate):- Pins against the complete Leaf certificate DER hash
- Strictest validation — breaks on any renewal
- Best for maximum security on services you control
Intermediate CA Pinning
-
Intermediate Public Key Pinning (
PinningMethod.intermediatePublicKey):- Pins against the Intermediate CA's public key
- Most stable option: survives Leaf rotations and CA renewals that keep the same key
- ✅ Recommended for services behind CDN/WAF (Imperva, Cloudflare, Akamai)
-
Intermediate Certificate Pinning (
PinningMethod.intermediateCertificate):- Pins against the complete Intermediate CA certificate DER hash
- More restrictive than Intermediate Public Key
- Useful when you need to ensure a specific Intermediate CA is in the chain
Certificate Hash Generation #
Choose the appropriate method based on your pinning strategy:
Caution
Always use the bare domain with port (e.g., example.com:443) in the openssl s_client -connect command.
Do NOT include https:// — using https://example.com:443 will resolve to a different host or fail silently, producing a hash that will not match what Rust/rustls computes at runtime.
Leaf Public Key Hash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| openssl base64
Leaf Certificate Hash
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -outform DER \
| openssl dgst -sha256 -binary \
| openssl base64
Intermediate CA Public Key Hash
Use the -showcerts flag and extract the second certificate (index 1) in the chain:
# Save all certs in the chain
openssl s_client -showcerts -connect jsonplaceholder.typicode.com:443 -servername jsonplaceholder.typicode.com </dev/null 2>/dev/null \
| awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{ if(/BEGIN/){n++}; if(n==2) print }' \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| openssl base64
Intermediate CA Certificate Hash
openssl s_client -showcerts -connect jsonplaceholder.typicode.com:443 -servername jsonplaceholder.typicode.com </dev/null 2>/dev/null \
| awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{ if(/BEGIN/){n++}; if(n==2) print }' \
| openssl x509 -outform DER \
| openssl dgst -sha256 -binary \
| openssl base64
Basic Implementation #
import 'package:smart_dev_pinning_plugin/smart_dev_pinning_plugin.dart';
final client = SecureClient();
// Public Key Pinning (Leaf)
final response = await client.httpRequest(
certificateHash: "/UzJAZYxLBnEpBwXAcmd4WHi7f8aYgfMExGnoyp5B04=",
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/1',
headers: {'Content-Type': 'application/json; charset=UTF-8'},
pinningMethod: PinningMethod.publicKey,
);
if (response.success) {
print('Data: ${response.data}');
} else {
print('Error [${response.errorType}]: ${response.error}');
}
// Intermediate Public Key Pinning (Recommended for CDN/WAF services)
final cdnResponse = await client.httpRequest(
certificateHash: "INTERMEDIATE_CA_PUBKEY_HASH_HERE",
method: 'GET',
url: 'https://cdn-backed-service.example.com/api',
headers: {'Content-Type': 'application/json'},
pinningMethod: PinningMethod.intermediatePublicKey,
);
if (cdnResponse.success) {
print('CDN response: ${cdnResponse.data}');
} else {
print('Error [${cdnResponse.errorType}]: ${cdnResponse.error}');
}
// Certificate Pinning (Leaf) with POST
final postResponse = await client.httpRequest(
certificateHash: "YOUR_CERTIFICATE_HASH_HERE",
method: 'POST',
url: 'https://api.example.com/data',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your_token_here'
},
body: {'key': 'value'},
pinningMethod: PinningMethod.certificate,
);
if (postResponse.success) {
print('POST response: ${postResponse.data}');
} else {
print('Error [${postResponse.errorType}]: ${postResponse.error}');
}
Advanced Usage Examples #
Handling Different Request Types
final client = SecureClient();
// GET request with query parameters
final getResponse = await client.httpRequest(
certificateHash: "your_hash_here",
method: 'GET',
url: 'https://api.example.com/users?page=1&limit=10',
headers: {'Accept': 'application/json'},
pinningMethod: PinningMethod.publicKey,
);
// POST with JSON body
final postResponse = await client.httpRequest(
certificateHash: "your_hash_here",
method: 'POST',
url: 'https://api.example.com/users',
headers: {'Content-Type': 'application/json'},
body: {
'name': 'John Doe',
'email': 'john@example.com'
},
pinningMethod: PinningMethod.publicKey,
);
// PUT with string body
final putResponse = await client.httpRequest(
certificateHash: "your_hash_here",
method: 'PUT',
url: 'https://api.example.com/users/123',
headers: {'Content-Type': 'text/plain'},
body: 'Updated user data',
pinningMethod: PinningMethod.publicKey,
);
Performance Benchmarking #
The plugin includes comprehensive benchmarking tools to analyze the performance impact of SSL pinning:
// The example app provides detailed performance analysis including:
// - Response time comparisons (Standard HTTP vs SSL Pinned requests)
// - Throughput measurements and statistical analysis
// - Visual performance metrics with charts and graphs
// - Memory usage and CPU impact assessment
// - Network latency analysis with SSL pinning overhead
Error Handling #
The plugin returns a SmartResponse object that encapsulates both success and error states — SSL pinning failures are not thrown as exceptions, they are returned in the response:
final response = await client.httpRequest(
certificateHash: "invalid_hash",
method: 'GET',
url: 'https://example.com/api',
headers: {},
pinningMethod: PinningMethod.publicKey,
);
if (response.success) {
print('Data: ${response.data}');
print('Status code: ${response.statusCode}');
} else {
// Check the errorType to determine the cause
switch (response.errorType) {
case 'ConnectionError':
// SSL pinning mismatch, timeout, or connection failure
print('Connection failed: ${response.error}');
break;
case 'SSLPinningError':
// Client could not be created with the given hash
print('SSL pinning setup error: ${response.error}');
break;
case 'HttpError':
// Server returned a non-2xx status code
print('HTTP ${response.statusCode}: ${response.error}');
break;
case 'RequestError':
// Other request-level failures
print('Request error: ${response.error}');
break;
default:
print('Unknown error: ${response.error}');
}
}
Error Types Reference
errorType |
When it occurs |
|---|---|
ConnectionError |
SSL pinning hash mismatch, TLS handshake failure, timeout, DNS resolution fail |
SSLPinningError |
Invalid certificate hash or TLS client could not be created |
HttpError |
Server responded with a non-2xx HTTP status code |
RequestError |
Other request-level failures (malformed URL, etc.) |
ResponseReadError |
Response body could not be read |
InvalidMethodError |
Unsupported HTTP method |
JSONParseError |
Native response could not be parsed as JSON (Flutter-side) |
Note: SSL pinning mismatches surface as
ConnectionError(not a specific TLS error type) becauserustlswraps the failure as a connection-level error. Check theerrormessage string for details like"hash does not match".
Architecture Overview #
Android Implementation #
- Native Layer: Rust-based HTTP client with OpenSSL for TLS operations
- FFI Bridge: Asynchronous FFI calls using Flutter's
compute()function - Isolate Execution: Prevents UI blocking during network operations
iOS Implementation #
- Native Layer: Swift implementation using URLSession with custom certificate validation
- Method Channel: Direct communication between Dart and Swift code
- Background Queue: Network operations executed on background threads
API Reference #
SecureClient #
The main class for performing secure HTTP requests with certificate pinning.
Constructor
final client = SecureClient();
Creates a singleton instance of the secure HTTP client.
Methods
httpRequest()
Performs an HTTP request with SSL certificate pinning.
Future<SmartResponse> httpRequest({
required String method,
required String url,
Object? body,
Map<String, String>? headers,
String? encoding,
required String certificateHash,
required PinningMethod pinningMethod,
})
Parameters:
| Parameter | Type | Description | Required |
|---|---|---|---|
method |
String |
HTTP method ('GET', 'POST', 'PUT', 'DELETE', etc.) | ✅ |
url |
String |
Target URL for the request | ✅ |
body |
Object? |
Request body (String, Map, or null) | ❌ |
headers |
Map<String, String>? |
HTTP headers as key-value pairs | ❌ |
encoding |
String? |
Character encoding (default: 'json') | ❌ |
certificateHash |
String |
Base64-encoded certificate or public key hash | ✅ |
pinningMethod |
PinningMethod |
Type of pinning validation to use | ✅ |
Returns: Future<SmartResponse> — Contains success, data, statusCode, error, and errorType fields.
Throws:
ArgumentError— If body is not a String, Map, or nullUnsupportedError— If platform is not Android or iOS
Note: Network and SSL errors are not thrown. They are returned inside the
SmartResponseobject withsuccess: falseand the correspondingerrorType.
PinningMethod Enum #
Defines the certificate pinning validation strategy:
enum PinningMethod {
certificate, // Leaf certificate DER hash
publicKey, // Leaf public key (SPKI) hash
intermediateCertificate, // Intermediate CA certificate DER hash
intermediatePublicKey, // Intermediate CA public key (SPKI) hash
}
Extension Methods
extension PinningMethodExtension on PinningMethod {
String get value; // Returns string representation for native calls
}
| Enum Value | Native String | Use Case |
|---|---|---|
certificate |
"certificate" |
Own server, maximum strictness |
publicKey |
"publickey" |
Own server, flexible renewals |
intermediateCertificate |
"intermediatecertificate" |
CDN/WAF, stricter intermediate check |
intermediatePublicKey |
"intermediatepublickey" |
CDN/WAF, maximum stability ✅ |
Utility Functions #
parseResponse(String response)
Parses and validates HTTP responses for SSL pinning errors.
String parseResponse(String response)
Parameters:
response- Raw response string from native layer
Returns: Validated response string
Throws: Exception if response contains connection errors
Example Application #
The plugin includes a comprehensive example app demonstrating:
- Interactive SSL Pinning Testing: Real-time validation with multiple endpoints
- Performance Benchmarking Suite: Detailed performance analysis tools
- Visual Metrics Dashboard: Charts comparing standard vs secure clients
- Error Handling Demonstrations: Examples of handling various failure scenarios
- Best Practices Guide: Implementation patterns and security recommendations
To run the example:
cd example
flutter run
Platform Support #
| Platform | Status | Implementation |
|---|---|---|
| Android | ✅ Fully Supported | FFI + Rust + OpenSSL |
| iOS | ✅ Fully Supported | Method Channel + Swift + URLSession |
| Web | ❌ Not Supported | Browser security limitations |
| Desktop | ❌ Not Supported | Planned for future releases |
Security Considerations #
- Certificate Rotation: Use public key pinning for easier certificate updates
- CDN/WAF Services: Use
intermediatePublicKey— Leaf certificates vary across edge nodes and rotate frequently - Backup Pins: Consider implementing backup certificate pins for redundancy
- Pin Updates: Implement a strategy for updating certificate hashes in production
- Fallback Strategy: Plan for graceful degradation if pinning fails
- Testing: Thoroughly test pinning with staging environments before production
- Pin Stability: Intermediate CA public keys typically last 5–10 years vs 90 days for Leaf certificates
Troubleshooting #
Common Issues #
-
SSL Pinning Error: Certificate hash mismatch
- Verify the certificate hash is correct and up-to-date
- Check if the certificate has been renewed
-
Platform Not Supported: Using on unsupported platforms
- Ensure you're running on Android or iOS
- Check minimum version requirements
-
Network Timeouts: Slow network responses
- Consider implementing timeout handling
- Use performance benchmarking to identify bottlenecks
Contributing #
We welcome contributions! Please see our contributing guidelines and code of conduct.
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog #
See CHANGELOG.md for detailed version history and updates.