SSL Pinning

Secure network communication in Flutter

🔐 What is SSL Pinning?

SSL Pinning validates server certificates to prevent man-in-the-middle attacks. Flutter apps use certificate pinning to ensure they only communicate with trusted servers, protecting sensitive data during network transmission from interception and tampering.


// Basic SSL pinning setup
final client = HttpClient()
  ..badCertificateCallback = 
    (cert, host, port) => cert.sha1 == expectedSha1;
                                    

Key SSL Pinning Concepts

📜

Certificate Pinning

Validate server's SSL certificate against a known trusted certificate stored in app

SecurityContext context = 
  SecurityContext()
    ..setTrustedCertificates('cert.pem');
🔑

Public Key Pinning

Pin the server's public key instead of entire certificate for flexible security

cert.sha1 == 
  expectedPublicKeyHash
🛡️

MITM Protection

Prevents man-in-the-middle attacks by rejecting untrusted or fake certificates

badCertificateCallback = 
  (cert, host, port) => 
    validateCert(cert);
🌐

HTTPS Security

Ensures all network requests use secure HTTPS protocol with verified certificates

final response = 
  await http.get(
    Uri.parse('https://api.example.com')
  );

🔹 Setup SSL Pinning Package

Add http_certificate_pinning package to your project:

# pubspec.yaml
dependencies:
  http_certificate_pinning: ^2.1.1
// Import the package
import 'package:http_certificate_pinning/http_certificate_pinning.dart';

Result:

SSL pinning library is ready to secure your network communications.

🔹 Basic Certificate Pinning

Implement certificate pinning for API calls:

import 'package:http_certificate_pinning/http_certificate_pinning.dart';

class ApiService {
  Future checkCertificate() async {
    List allowedSHAFingerprints = [
      'E6:3C:50:6D:75:A1:5E:5C:8B:9F:3D:2A:1B:4C:7E:8F:9A:2D:3E:4F',
    ];

    try {
      await HttpCertificatePinning.check(
        serverURL: 'https://api.example.com',
        headerHttp: {'Content-Type': 'application/json'},
        sha: SHA.SHA256,
        allowedSHAFingerprints: allowedSHAFingerprints,
        timeout: 60,
      );
      print('Certificate is valid');
    } catch (e) {
      print('Certificate validation failed: $e');
    }
  }
}

Result:

API calls only succeed if server certificate matches the pinned fingerprint.

🔹 Custom HttpClient with Pinning

Create a custom HTTP client with certificate validation:

import 'dart:io';

class SecureHttpClient {
  final String expectedSha256 = 
    'E63C506D75A15E5C8B9F3D2A1B4C7E8F9A2D3E4F...';

  HttpClient createSecureClient() {
    final client = HttpClient();
    
    client.badCertificateCallback = 
      (X509Certificate cert, String host, int port) {
        // Get certificate SHA-256 fingerprint
        final certSha256 = cert.sha1.toString();
        
        // Compare with expected fingerprint
        return certSha256 == expectedSha256;
      };
    
    return client;
  }

  Future makeSecureRequest(String url) async {
    final client = createSecureClient();
    
    try {
      final request = await client.getUrl(Uri.parse(url));
      final response = await request.close();
      final responseBody = await response.transform(utf8.decoder).join();
      return responseBody;
    } finally {
      client.close();
    }
  }
}

Result:

HTTP client validates certificate before making any network request.

🔹 Get Certificate Fingerprint

Extract certificate fingerprint from your server:

# Using OpenSSL command line
openssl s_client -connect api.example.com:443 < /dev/null | \
  openssl x509 -fingerprint -sha256 -noout

# Output example:
# SHA256 Fingerprint=E6:3C:50:6D:75:A1:5E:5C:8B:9F:3D:2A:1B:4C:7E:8F
// Use this fingerprint in your Flutter app
final allowedFingerprints = [
  'E6:3C:50:6D:75:A1:5E:5C:8B:9F:3D:2A:1B:4C:7E:8F',
];

Result:

Server certificate fingerprint obtained and ready to use in your app.

🔹 Multiple Certificate Pinning

Pin multiple certificates for backup and rotation:

class MultiCertPinning {
  final List allowedFingerprints = [
    // Primary certificate
    'E6:3C:50:6D:75:A1:5E:5C:8B:9F:3D:2A:1B:4C:7E:8F',
    // Backup certificate
    'A1:B2:C3:D4:E5:F6:07:18:29:3A:4B:5C:6D:7E:8F:90',
  ];

  Future validateCertificate(String url) async {
    try {
      await HttpCertificatePinning.check(
        serverURL: url,
        headerHttp: {},
        sha: SHA.SHA256,
        allowedSHAFingerprints: allowedFingerprints,
        timeout: 60,
      );
      return true;
    } catch (e) {
      return false;
    }
  }
}

// Usage
final pinning = MultiCertPinning();
bool isValid = await pinning.validateCertificate('https://api.example.com');

Result:

App accepts multiple valid certificates, allowing for certificate rotation without app updates.

🔹 Dio with SSL Pinning

Use SSL pinning with Dio HTTP client:

# Add dependencies
dependencies:
  dio: ^5.3.3
  dio_http_certificate_pinning: ^1.0.0
import 'package:dio/dio.dart';
import 'package:dio_http_certificate_pinning/dio_http_certificate_pinning.dart';

class SecureDioClient {
  Dio createDioWithPinning() {
    final dio = Dio();
    
    dio.interceptors.add(
      CertificatePinningInterceptor(
        allowedSHAFingerprints: [
          'E6:3C:50:6D:75:A1:5E:5C:8B:9F:3D:2A:1B:4C:7E:8F',
        ],
      ),
    );
    
    return dio;
  }

  Future makeRequest() async {
    final dio = createDioWithPinning();
    return await dio.get('https://api.example.com/data');
  }
}

Result:

Dio client automatically validates certificates for all requests.

🔹 Handle Pinning Failures

Gracefully handle certificate validation failures:

class SecureApiClient {
  Future> fetchData(String url) async {
    try {
      await HttpCertificatePinning.check(
        serverURL: url,
        headerHttp: {'Content-Type': 'application/json'},
        sha: SHA.SHA256,
        allowedSHAFingerprints: ['E6:3C:50:6D:75:A1:5E:5C...'],
        timeout: 60,
      );
      
      // Certificate valid, make request
      final response = await http.get(Uri.parse(url));
      return jsonDecode(response.body);
      
    } on CertificatePinningException catch (e) {
      print('Certificate pinning failed: ${e.message}');
      throw Exception('Insecure connection detected');
    } catch (e) {
      print('Network error: $e');
      throw Exception('Failed to fetch data');
    }
  }
}

Result:

App detects and handles certificate validation failures with appropriate error messages.

🧠 Test Your Knowledge

What type of attack does SSL Pinning prevent?