What is SSL Pinning

Answer

Overview

SSL Pinning (also called Certificate Pinning) is a security technique that prevents Man-in-the-Middle (MITM) attacks by ensuring your app only trusts a specific SSL certificate or public key — not just any certificate signed by a trusted CA.


The Problem SSL Pinning Solves

Without pinning, if an attacker installs a rogue root CA on the device, they can intercept HTTPS traffic by presenting a "valid" certificate. SSL Pinning prevents this by hardcoding the expected certificate.

text
Without Pinning:
App → [Attacker's Proxy with fake CA] → Server
App accepts the rogue certificate — traffic intercepted ❌

With Pinning:
App → [Attacker's Proxy] → Server
App rejects the certificate (doesn't match pinned) ✅

Types of SSL Pinning

TypeWhat is PinnedFlexibility
Certificate PinningFull certificate❌ Must update app when cert expires
Public Key PinningPublic key only✅ Survives cert renewal (key stays same)
SPKI Hash PinningHash of public key✅ Most modern approach

Implementing SSL Pinning in Flutter

Method 1: Using dio + dio_pinning interceptor

dart
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';

Future<Dio> createPinnedDio() async {
  // Load certificate from assets
  final ByteData data = await rootBundle.load('assets/certs/server.cer');
  final Uint8List certBytes = data.buffer.asUint8List();

  final SecurityContext context = SecurityContext();
  context.setTrustedCertificatesBytes(certBytes);

  final HttpClient client = HttpClient(context: context);
  final adapter = IOHttpClientAdapter();
  adapter.createHttpClient = () => client;

  final dio = Dio();
  dio.httpClientAdapter = adapter;
  return dio;
}

Method 2: Using http_certificate_pinning package

yaml
dependencies:
  http_certificate_pinning: ^4.0.0
dart
// Check certificate before making request
try {
  final response = await HttpCertificatePinning.check(
    serverURL: 'https://api.example.com
    headerHttp: {'Accept': 'application/json'},
    sha: SHA.SHA256,
    allowedSHAFingerprints: ['YOUR_CERTIFICATE_SHA256_FINGERPRINT'],
    timeout: 60,
  );
} on PlatformException catch (e) {
  // Certificate doesn't match — possible MITM attack!
  print('Certificate pinning failed: $e');
}

Getting the Certificate Fingerprint

bash
# Get SHA-256 fingerprint of a server's certificate
openssl s_client -connect api.example.com:443 -servername api.example.com < /dev/null 2>/dev/null | \
  openssl x509 -fingerprint -sha256 -noout

# Output: SHA256 Fingerprint=AB:CD:EF:12:34:56...

Considerations

AspectDetail
Certificate expiryPin public key (not full cert) to survive renewals
Backup pinsAlways include at least 2 pins for rollback
DebuggingMust disable pinning in debug mode
App updateApp must be updated when pinned cert changes
Bypass riskRoot devices / Frida can bypass pinning

Debug vs Release

dart
// Only enable pinning in release builds
Future<Dio> createDio() async {
  final dio = Dio();
  const bool isProduction = bool.fromEnvironment('dart.vm.product');

  if (isProduction) {
    // Apply certificate pinning
    dio.httpClientAdapter = await _createPinnedAdapter();
  }

  return dio;
}

Key Point: SSL Pinning is an important layer of defense against MITM attacks, especially for financial or healthcare apps. Always pin the public key (not the full certificate) so you don't need to update the app when the certificate is renewed.