Skip to content

Implementing Certificate Pinning

Certificate pinning is a critical security measure that ensures your Flutter app communicates only with trusted servers by validating their SSL/TLS certificates against a predefined set of trusted certificates. This prevents man-in-the-middle (MITM) attacks and ensures data integrity during network requests.


Why Certificate Pinning Matters

Without certificate pinning, your app is vulnerable to: - MITM attacks where attackers intercept traffic between the app and the server. - Certificate spoofing where a malicious server mimics a legitimate one. - Manually configured trust stores that may include compromised certificates.

Pinning mitigates these risks by enforcing strict certificate validation.


Implementation Steps

1. Prepare Trusted Certificates

  • Extract the server's SSL certificate (e.g., example.com.crt) using tools like openssl:
    openssl s_client -connect example.com:43 -showcerts
    
  • Save the certificate to a file (e.g., assets/cert.pem).

2. Configure HTTP Client with Pinned Certificate

Use the http_certificate_pinning package to enforce certificate pinning. This library provides a streamlined API for pinning certificates with the http package.

Example: Using http_certificate_pinning for Certificate Pinning

import 'package:http_certificate_pinning/http_certificate_pinning.dart';
import 'package:http/http.dart' as http;

void main() async {
  // Load the trusted certificate from assets
  final certificate = await X509Certificate.fromPemFile('assets/cert.pem');

  // Create a pinned HTTP client
  final client = http.Client(
    pinCertificates: [certificate],
  );

  // Make a secure request
  final response = await client.get(Uri.parse('https://example.com'));
  print(await response.body);
}

3. Validate Server Certificates

Ensure the server's certificate matches the pinned one. For example, verify the subject and issuer, and check for expiration:

Example: Certificate Validation with Expiration Check

final certificate = await X509Certificate.fromPemFile('assets/cert.pem');
final serverCert = await request.connection!.serverCertificate;

if (serverCert != certificate) {
  throw Exception('Certificate mismatch');
}

// Check if certificate is expired
if (serverCert.notAfter.isBefore(DateTime.now())) {
  throw Exception('Certificate expired');
}

4. Handle Edge Cases

  • Expired Certificates: Reject connections if the certificate is expired (see validation example above).
  • Multiple Certificates: Pin all trusted certificates to avoid partial trust.
  • Dynamic Trust Stores: Update the trust store securely (e.g., via secure storage or OTA updates).

Diagram: Certificate Pinning Workflow

[App] → [Pinned HTTP Client] → [Server]
       ↑                        ↓
       └───────────[Trusted Cert]───────────┘

Key Takeaways

  • Certificate pinning prevents MITM attacks by validating server certificates.
  • Use the http_certificate_pinning package for a streamlined pinning workflow.
  • Always validate the server's certificate against the pinned one during TLS handshakes.
  • Securely update the trust store to handle certificate rotations or new trusted servers.