smart_dev_pinning_plugin 4.0.0
smart_dev_pinning_plugin: ^4.0.0 copied to clipboard
This plugin creates a secure native TLS connection to execute HTTP requests with certificate pinning.
example/lib/main.dart
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:smart_dev_pinning_plugin/smart_dev_pinning_plugin.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// --- Configuration State ---
final TextEditingController _urlController = TextEditingController(
text: 'https://jsonplaceholder.typicode.com/posts/1',
);
final TextEditingController _hashController = TextEditingController();
PinningMethod _selectedMethod = PinningMethod.certificate;
// Known valid hashes for jsonplaceholder.typicode.com (for demo purposes)
final Map<PinningMethod, String> _demoHashes = {
PinningMethod.certificate: "av2cD1Ongt8KXvUIHVMi09oimOn1RTkzIjmxtNDJP6g=",
PinningMethod.publicKey:
"k+swi1D7Mu27FDJ9DAfns27/YipZz5s7BezuYsaXM/s=", // Check actual value if it fails
PinningMethod.intermediateCertificate:
"HfwWBfutNY2LyET3bRUgP6ycpcGnn9SFf/ryhk++v5Y=",
PinningMethod.intermediatePublicKey:
"kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa/A4=",
};
// --- Execution State ---
String _response = "";
bool _isRequestRunning = false;
bool _isSuccess = false;
// --- Benchmark State ---
String benchmarkResults = "";
bool isBenchmarkRunning = false;
double? avgStandard;
double? avgSecure;
int? minStandard;
int? maxStandard;
int? minSecure;
int? maxSecure;
double? stdDevStandard;
double? stdDevSecure;
double? performanceImpact;
String? performanceVerdict;
String? verdictEmoji;
String? performanceAnalysis;
@override
void initState() {
super.initState();
_hashController.text = _demoHashes[_selectedMethod]!;
}
@override
void dispose() {
_urlController.dispose();
_hashController.dispose();
super.dispose();
}
void _onMethodChanged(PinningMethod? method) {
if (method != null) {
setState(() {
_selectedMethod = method;
// Auto-fill hash if it's the default URL
if (_urlController.text.contains('jsonplaceholder.typicode.com')) {
_hashController.text = _demoHashes[method] ?? "";
}
});
}
}
String _getMethodDescription(PinningMethod method) {
switch (method) {
case PinningMethod.certificate:
return 'Validates the complete Leaf certificate (DER hash). Most specific, requires hash update on renewal.';
case PinningMethod.publicKey:
return 'Validates only the Leaf public key (SPKI hash). More flexible, survives renewals if key is kept.';
case PinningMethod.intermediateCertificate:
return 'Validates the Intermediate CA certificate. Recommended for services behind CDN/WAF.';
case PinningMethod.intermediatePublicKey:
return 'Validates the Intermediate CA public key. Most stable option for CDN/WAF services.';
}
}
/// Makes an HTTP request using Dart's native HTTP client
Future<String> _makeStandardHttpRequest() async {
final httpClient = HttpClient();
try {
final request = await httpClient.getUrl(Uri.parse(_urlController.text));
request.headers.set('Content-type', 'application/json; charset=UTF-8');
final response = await request.close();
final responseBody = await response.transform(utf8.decoder).join();
return responseBody;
} finally {
httpClient.close();
}
}
/// Makes an HTTP request using the secure client with SSL Pinning
Future<SmartResponse> _makeSecureHttpRequest() async {
final client = SecureClient();
return await client.httpRequest(
certificateHashes: [_hashController.text.trim()],
method: 'GET',
url: _urlController.text.trim(),
headers: {'Content-type': 'application/json; charset=UTF-8'},
pinningMethod: _selectedMethod,
);
}
Future<void> _testConnection() async {
// Hide keyboard
FocusScope.of(context).unfocus();
setState(() {
_isRequestRunning = true;
_response = "Connecting...\nMethod: ${_selectedMethod.name}";
});
try {
final result = await _makeSecureHttpRequest();
setState(() {
_isSuccess = result.success;
if (result.success) {
_response =
"Code: ${result.statusCode}\n\nData:\n${_truncateData(result.data ?? 'No data', 300)}";
} else {
_response =
"Error Type: ${result.errorType}\nDetails: ${result.error}";
}
});
} catch (e) {
setState(() {
_isSuccess = false;
_response = "Exception: $e";
});
} finally {
setState(() {
_isRequestRunning = false;
});
}
}
String _truncateData(String data, int maxLength) {
if (data.length <= maxLength) return data;
return '${data.substring(0, maxLength)}...\n\n[Truncated for demo]';
}
/// Executes benchmark comparing both clients
Future<void> _runBenchmark() async {
// Hide keyboard
FocusScope.of(context).unfocus();
setState(() {
isBenchmarkRunning = true;
benchmarkResults = "Running benchmark tests...";
// Reset previous data
avgStandard = null;
avgSecure = null;
});
const int iterations = 10;
final List<Duration> standardTimes = [];
final List<Duration> secureTimes = [];
try {
// Benchmark standard client
for (int i = 0; i < iterations; i++) {
final stopwatch = Stopwatch()..start();
await _makeStandardHttpRequest();
stopwatch.stop();
standardTimes.add(stopwatch.elapsed);
setState(() {
benchmarkResults =
"Testing Standard HTTP Client: ${i + 1}/$iterations";
});
await Future.delayed(const Duration(milliseconds: 50));
}
// Benchmark secure client
for (int i = 0; i < iterations; i++) {
final stopwatch = Stopwatch()..start();
try {
await _makeSecureHttpRequest();
} catch (e) {
// Continue even with pinning errors
}
stopwatch.stop();
secureTimes.add(stopwatch.elapsed);
setState(() {
benchmarkResults =
"Testing Secure SSL Pinning Client: ${i + 1}/$iterations";
});
await Future.delayed(const Duration(milliseconds: 50));
}
// Calculate comprehensive statistics
final avgStandardCalc =
standardTimes.map((d) => d.inMilliseconds).reduce((a, b) => a + b) /
iterations;
final avgSecureCalc =
secureTimes.map((d) => d.inMilliseconds).reduce((a, b) => a + b) /
iterations;
final minStandardCalc = standardTimes
.map((d) => d.inMilliseconds)
.reduce((a, b) => a < b ? a : b);
final maxStandardCalc = standardTimes
.map((d) => d.inMilliseconds)
.reduce((a, b) => a > b ? a : b);
final minSecureCalc = secureTimes
.map((d) => d.inMilliseconds)
.reduce((a, b) => a < b ? a : b);
final maxSecureCalc = secureTimes
.map((d) => d.inMilliseconds)
.reduce((a, b) => a > b ? a : b);
final stdDevStandardCalc = _calculateStandardDeviation(
standardTimes,
avgStandardCalc,
);
final stdDevSecureCalc = _calculateStandardDeviation(
secureTimes,
avgSecureCalc,
);
final performanceImpactCalc =
((avgSecureCalc - avgStandardCalc) / avgStandardCalc * 100);
String performanceVerdictCalc;
String verdictEmojiCalc;
String performanceAnalysisCalc;
if (performanceImpactCalc < -5) {
performanceVerdictCalc = "Faster than standard!";
verdictEmojiCalc = "🚀";
performanceAnalysisCalc =
"The secure client outperforms standard HTTP by ${(-performanceImpactCalc).toStringAsFixed(1)}%. Due to Rust's optimized connection pool and fast TLS handshakes.";
} else if (performanceImpactCalc < 5) {
performanceVerdictCalc = "Excellent performance";
verdictEmojiCalc = "🟢";
performanceAnalysisCalc =
"SSL Certificate Pinning adds negligible overhead (${performanceImpactCalc.toStringAsFixed(1)}%) while providing critical security benefits.";
} else if (performanceImpactCalc < 15) {
performanceVerdictCalc = "Good performance";
verdictEmojiCalc = "🟡";
performanceAnalysisCalc =
"Moderate ${performanceImpactCalc.toStringAsFixed(1)}% performance impact. Fully acceptable for secure API calls.";
} else {
performanceVerdictCalc = "Noticeable overhead";
verdictEmojiCalc = "🟠";
performanceAnalysisCalc =
"There's a ${performanceImpactCalc.toStringAsFixed(1)}% performance overhead compared to standard Dart client.";
}
setState(() {
avgStandard = avgStandardCalc;
avgSecure = avgSecureCalc;
minStandard = minStandardCalc;
maxStandard = maxStandardCalc;
minSecure = minSecureCalc;
maxSecure = maxSecureCalc;
stdDevStandard = stdDevStandardCalc;
stdDevSecure = stdDevSecureCalc;
performanceImpact = performanceImpactCalc;
performanceVerdict = performanceVerdictCalc;
verdictEmoji = verdictEmojiCalc;
performanceAnalysis = performanceAnalysisCalc;
benchmarkResults =
"Benchmark completed successfully with $iterations iterations";
});
} catch (e) {
setState(() {
benchmarkResults = "❌ Benchmark Error: $e";
});
} finally {
setState(() {
isBenchmarkRunning = false;
});
}
}
double _calculateStandardDeviation(List<Duration> times, double mean) {
if (times.isEmpty) return 0.0;
final sumSquaredDiffs = times
.map((time) => time.inMilliseconds - mean)
.map((diff) => diff * diff)
.reduce((a, b) => a + b);
return sqrt(sumSquaredDiffs / times.length);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Smart Dev SSL Pinning',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF0F172A), // Slate 900
primary: const Color(0xFF2563EB), // Blue 600
secondary: const Color(0xFF10B981), // Emerald 500
tertiary: const Color(0xFF8B5CF6), // Violet 500
surface: Colors.white,
),
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: 0,
scrolledUnderElevation: 2,
backgroundColor: Color(0xFF0F172A),
foregroundColor: Colors.white,
titleTextStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
cardTheme: const CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
side: BorderSide(color: Color(0xFFE2E8F0)), // Slate 200
),
color: Colors.white,
margin: EdgeInsets.only(bottom: 20),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: const Color(0xFFF1F5F9), // Slate 100
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF2563EB), width: 2),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
home: Scaffold(
appBar: AppBar(
title: const Text('Smart Dev Settings'),
actions: [
IconButton(
icon: const Icon(Icons.shield),
tooltip: 'SSL Pinning',
onPressed: () {},
),
],
),
body: SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 20.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildConfigurationCard(),
if (_response.isNotEmpty || _isRequestRunning)
_buildResultsCard(),
_buildBenchmarkCard(),
],
),
),
),
),
);
}
Widget _buildConfigurationCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.settings_ethernet,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(width: 12),
const Text(
'Connection Setup',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
),
],
),
const SizedBox(height: 24),
// URL Input
const Text(
'API Endpoint',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF64748B),
),
),
const SizedBox(height: 8),
TextField(
controller: _urlController,
decoration: const InputDecoration(
hintText: 'https://api.example.com/data',
prefixIcon: Icon(
Icons.link,
color: Color(0xFF94A3B8),
), // Slate 400
),
keyboardType: TextInputType.url,
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 20),
// Pinning Method Dropdown
const Text(
'Pinning Method',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF64748B),
),
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(12),
),
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<PinningMethod>(
value: _selectedMethod,
isExpanded: true,
icon: const Icon(
Icons.keyboard_arrow_down,
color: Color(0xFF94A3B8),
),
borderRadius: BorderRadius.circular(12),
items:
PinningMethod.values.map((method) {
return DropdownMenuItem(
value: method,
child: Text(
method.name
.replaceAll('intermediate', 'Intermediate ')
.replaceAll(RegExp(r'(?<!^)(?=[A-Z])'), ' '),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
);
}).toList(),
onChanged: _onMethodChanged,
),
),
),
),
// Method Description
Padding(
padding: const EdgeInsets.only(top: 8, left: 4, right: 4),
child: Text(
_getMethodDescription(_selectedMethod),
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
height: 1.4,
),
),
),
const SizedBox(height: 20),
// Hash Input
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'SHA-256 Hash (Base64)',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF64748B),
),
),
GestureDetector(
onTap: () {
Clipboard.setData(
ClipboardData(text: _hashController.text),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Hash copied to clipboard'),
duration: Duration(seconds: 1),
),
);
},
child: const Text(
'Copy',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Color(0xFF2563EB),
),
),
),
],
),
const SizedBox(height: 8),
TextField(
controller: _hashController,
decoration: const InputDecoration(
hintText: 'Enter base64 encoded SHA-256 hash',
prefixIcon: Icon(Icons.fingerprint, color: Color(0xFF94A3B8)),
),
style: const TextStyle(fontSize: 14, fontFamily: 'monospace'),
),
const SizedBox(height: 24),
// Action Button
ElevatedButton(
onPressed: _isRequestRunning ? null : _testConnection,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
),
child:
_isRequestRunning
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.rocket_launch, size: 20),
SizedBox(width: 8),
Text(
'Test Connection',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
),
);
}
Widget _buildResultsCard() {
Color statusColor = Colors.grey;
IconData statusIcon = Icons.pending;
if (!_isRequestRunning) {
statusColor =
_isSuccess ? const Color(0xFF10B981) : const Color(0xFFEF4444);
statusIcon = _isSuccess ? Icons.check_circle : Icons.error;
}
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(16),
),
border: Border(
bottom: BorderSide(color: statusColor.withOpacity(0.2)),
),
),
child: Row(
children: [
Icon(statusIcon, color: statusColor, size: 20),
const SizedBox(width: 10),
Text(
_isRequestRunning
? 'Executing Request...'
: (_isSuccess ? 'Response Received' : 'Request Failed'),
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: statusColor,
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(20),
child:
_isRequestRunning
? const Center(
child: Padding(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(),
),
)
: Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1E293B), // Slate 800
borderRadius: BorderRadius.circular(12),
),
child: Text(
_response,
style: const TextStyle(
fontFamily: 'monospace',
color: Color(0xFFE2E8F0),
fontSize: 13,
height: 1.5,
),
),
),
),
],
),
);
}
Widget _buildBenchmarkCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.tertiary.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.speed,
color: Theme.of(context).colorScheme.tertiary,
),
),
const SizedBox(width: 12),
const Text(
'Performance Benchmark',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
),
],
),
const SizedBox(height: 12),
const Text(
'Compare standard Dart HTTP client vs inside-Rust secure client.',
style: TextStyle(fontSize: 13, color: Color(0xFF64748B)),
),
const SizedBox(height: 20),
if (avgStandard != null && avgSecure != null) ...[
_buildBenchmarkVisualization(),
const SizedBox(height: 20),
] else if (isBenchmarkRunning) ...[
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Column(
children: [
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(
benchmarkResults,
style: const TextStyle(color: Color(0xFF64748B)),
),
],
),
),
),
const SizedBox(height: 20),
],
OutlinedButton.icon(
onPressed: isBenchmarkRunning ? null : _runBenchmark,
icon: const Icon(Icons.analytics),
label: const Text(
'Run Benchmark',
style: TextStyle(fontWeight: FontWeight.w600),
),
style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.tertiary,
side: BorderSide(
color: Theme.of(
context,
).colorScheme.tertiary.withOpacity(0.5),
),
padding: const EdgeInsets.symmetric(vertical: 14),
),
),
],
),
),
);
}
Widget _buildBenchmarkVisualization() {
final isSecureFaster = performanceImpact! < 0;
final absoluteDiff = (avgSecure! - avgStandard!).abs();
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFF8FAFC),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(verdictEmoji ?? '📊', style: const TextStyle(fontSize: 24)),
const SizedBox(width: 8),
Expanded(
child: Text(
performanceVerdict ?? 'Results',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color:
isSecureFaster
? const Color(0xFF059669)
: const Color(0xFFD97706),
),
textAlign: TextAlign.center,
),
),
],
),
const SizedBox(height: 20),
// Main Comparison
_buildPerformanceBar(
'Standard HTTP',
avgStandard!,
avgStandard! > avgSecure! ? avgStandard! : avgSecure!,
const Color(0xFF64748B),
),
const SizedBox(height: 12),
_buildPerformanceBar(
'Secure SSL Pinning',
avgSecure!,
avgStandard! > avgSecure! ? avgStandard! : avgSecure!,
isSecureFaster ? const Color(0xFF10B981) : const Color(0xFFF59E0B),
),
const SizedBox(height: 20),
const Divider(height: 1, color: Color(0xFFE2E8F0)),
const SizedBox(height: 16),
// Stats Grid
Row(
children: [
Expanded(
child: _buildMetricCard(
'Diff',
'${performanceImpact! > 0 ? '+' : '-'}${absoluteDiff.toStringAsFixed(1)}ms',
),
),
const SizedBox(width: 12),
Expanded(
child: _buildMetricCard(
'Impact',
'${performanceImpact! >= 0 ? '+' : ''}${performanceImpact!.toStringAsFixed(1)}%',
),
),
const SizedBox(width: 12),
Expanded(
child: _buildMetricCard(
'Std Dev',
'${((stdDevStandard! + stdDevSecure!) / 2).toStringAsFixed(1)}ms',
),
),
],
),
],
),
);
}
Widget _buildPerformanceBar(
String title,
double value,
double maxValue,
Color color,
) {
final percentage = (value / maxValue).clamp(0.0, 1.0);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Color(0xFF334155),
),
),
Text(
'${value.toStringAsFixed(1)}ms',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
const SizedBox(height: 6),
Container(
height: 12,
decoration: BoxDecoration(
color: const Color(0xFFE2E8F0),
borderRadius: BorderRadius.circular(6),
),
child: FractionallySizedBox(
widthFactor: percentage,
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(6),
),
),
),
),
],
);
}
Widget _buildMetricCard(String label, String value) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
child: Column(
children: [
Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A),
),
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
fontSize: 11,
color: Color(0xFF64748B),
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}