DEV Community

Cover image for Stop Bypassing SSL in Flutter: Solving the Missing Intermediate Certificate Nightmare with AIA Chasing
Yadnesh Teli
Yadnesh Teli

Posted on AI-assisted

Stop Bypassing SSL in Flutter: Solving the Missing Intermediate Certificate Nightmare with AIA Chasing

Stop Bypassing SSL in Flutter: Solving the Missing Intermediate Certificate Nightmare with AIA Chasing

Every Flutter developer has run into this maddening phantom bug at least once in their career:

You open a client's API endpoint or an in-app WebView. In your desktop browser (Chrome, Edge, Safari), the URL loads smoothly with a reassuring green padlock. Everything looks clean and trusted.

Then you test the exact same URL inside your Flutter app on Android:

HandshakeException: Handshake error in client (OS Error: 
  CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate)
Enter fullscreen mode Exit fullscreen mode

Or if you're loading an in-app WebView with webview_flutter:

onReceivedSslError: primaryError: SSL_UNTRUSTED (net::ERR_CERT_AUTHORITY_INVALID)
Enter fullscreen mode Exit fullscreen mode

You message the backend infrastructure team, and they show you a screenshot of Chrome: "The certificate is valid, issued by a major authority (like Sectigo or DigiCert), and expires next year. The bug must be in your mobile app."

Now you're stuck between a rock and a hard place. And that's usually the moment a well-meaning developer turns to StackOverflow and finds the worst advice in mobile engineering history.


The Dangerous "Quick Fix" We Must Stop Doing

Search for CERTIFICATE_VERIFY_FAILED on StackOverflow, and the most upvoted answers will instruct you to add this:

// ⚠️ NEVER DO THIS IN PRODUCTION:
class MyHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    return super.createHttpClient(context)
      ..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
  }
}
Enter fullscreen mode Exit fullscreen mode

Or in your WebView:

// ⚠️ INSTANT SECURITY HOLE:
onReceivedSslError: (controller, error) {
  handler.proceed(); // Blindly accepts any certificate
}
Enter fullscreen mode Exit fullscreen mode

Why this is a catastrophe

Setting badCertificateCallback = (...) => true or unconditionally calling proceed() completely eliminates TLS encryption verification.

When you do this, your app will accept any certificate — including a self-signed cert from an attacker sitting next to your user on public Wi-Fi running mitmproxy or Charles Proxy. All authentication tokens, session cookies, passwords, and private PII can now be intercepted and decrypted in plaintext.

Google Play's security scanner actively flags apps that call handler.proceed() or disable badCertificateCallback, leading to app rejections and suspensions.


The Root Cause: Why Chrome Works and Mobile Fails

To fix this properly without compromising security, we have to understand what is actually happening.

As explained in merlonix's breakdown of SSL issuer problems, web servers frequently send an incomplete certificate chain:

  1. A server sends only its leaf certificate (cert.pem), omitting the intermediate certificate (chain.pem).
  2. The client's operating system only trusts Root CAs (like USERTrust or DigiCert Global Root). It has never seen the specific Intermediate CA that signed this leaf cert.

Why does desktop Chrome work?

Desktop browsers implement a feature defined in RFC 5280 §4.2.2.1 called Authority Information Access (AIA):

  1. Chrome parses the leaf certificate's X.509 extensions and finds the caIssuers URI (e.g., http://crt.sectigo.com/SectigoRSADomainValidationSecureServerCA.crt).
  2. Chrome quietly makes an HTTP GET request in the background to download the missing intermediate certificate.
  3. It stitches the leaf, intermediate, and root together into a full cryptographic chain and verifies it against the OS root store.

Why does Flutter fail?

Mobile runtimes — specifically dart:io (SecurityContext) and Android's native TrustManager — do not implement AIA chasing by default. They expect the server to provide the full chain (fullchain.pem) in the TLS ServerHello handshake. When the server fails to do so, mobile clients throw unable to get local issuer certificate.

And often, you don't control the server! It might be a third-party payment gateway, an enterprise partner API, an exhibitor portal, or a legacy government service where submitting an IT ticket to update Nginx/Apache takes three months.


Enter AIA Chaser: Automated, Scoped Certificate Chasing for Flutter

Rather than choosing between a broken app and a gaping security hole, I built and open-sourced AIA Chaser (aia_chaser).

AIA Chaser brings desktop-grade Authority Information Access chasing directly into Flutter and Android, without disabling certificate validation.

AIA Certificate Chain Completion Diagram

Key Principles

  1. Cryptographic Integrity First: It never blind-accepts untrusted certificates. If a certificate is self-signed, expired, forged, or belongs to an unlisted host, it is strictly rejected.
  2. Host-Scoped Policies: You explicitly define which hostnames or wildcards (*.example.com) are allowed to chase certificates.
  3. Microsecond In-Memory Cache: Chased certificates are held in an in-memory leaf store. Once validated, subsequent requests hit cache in 1–3ms with zero network overhead.
  4. Persistent LRU Disk Cache: Validated intermediates are stored in an encrypted/private disk cache so cold app restarts don't re-download certificates over cellular networks.
  5. Zero-Latency Prewarming: Validate your mission-critical endpoints during app startup in the background (PrewarmMode.background) so user-facing calls never wait on an AIA fetch.

The Complete Ecosystem

AIA Chaser is modular and published across 4 companion packages on pub.dev:

Package Pub.dev Score Role
aia_chaser 150 / 160 Core engine, Pigeon IPC platform channel, Android TrustManager validator, cache, and diagnostic view.
aia_chaser_dio 160 / 160 🎯 Custom HttpClientAdapter for dio with transparent retry-once resilience.
aia_chaser_http 160 / 160 🎯 Drop-in http.BaseClient for package:http.
aia_chaser_webview 160 / 160 🎯 Safe Android onReceivedSslError handler for webview_flutter.

Quick Start (Less than 10 Lines)

1. Installation

In your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  aia_chaser: ^0.1.0
  aia_chaser_dio: ^0.1.0
  aia_chaser_webview: ^0.1.0
Enter fullscreen mode Exit fullscreen mode

2. Initialize in main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:aia_chaser/aia_chaser.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await AiaChaser.initialize(
    AiaConfig(
      // Pre-validates hosts at startup in background:
      prewarm: PrewarmMode.background,
      hosts: {
        'api.partner-service.com': const HostPolicy.chase(retryOnce: true),
        '*.exhibitor-portal.in': const HostPolicy.chase(),
      },
    ),
  );

  // Installs safe global dart:io overrides
  HttpOverrides.global = AiaHttpOverrides();

  runApp(const MyApp());
}
Enter fullscreen mode Exit fullscreen mode

3. Safe Android WebView Integration

Instead of calling handler.proceed() blindly in onReceivedSslError, let AIA Chaser verify the chain first:

import 'package:aia_chaser_webview/aia_chaser_webview.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';

final controller = WebViewController()
  ..setNavigationDelegate(
    NavigationDelegate(
      // Standard navigation handling...
    ),
  );

// Wire safe AIA verification into Android WebView
if (controller.platform is AndroidWebViewController) {
  AiaWebViewHandler.install(controller.platform as AndroidWebViewController);
}
Enter fullscreen mode Exit fullscreen mode

If the server sends a partial chain, AiaWebViewHandler will:

  1. Intercept SSL_UNTRUSTED.
  2. Extract the leaf cert and chase the intermediate.
  3. Validate against Android's system trust store (X509TrustManagerExtensions).
  4. Only call proceed() if the chain is cryptographically verified.
  5. Otherwise, safely reject the connection and display your custom error UI.

Real-World Production Case Study

We recently deployed AIA Chaser in a production trade-expo companion app (ACREX India).

The event portal (register.acrex.in) hosted real-time exhibitor directories (254 exhibitors) and interactive 3D floorplan WebViews. The server's SSL certificate was missing the Sectigo RSA intermediate certificate.

Prior to AIA Chaser:

  • The app was intermittently crashing with HandshakeException: CERTIFICATE_VERIFY_FAILED.
  • In-app WebViews were failing with white screens and SSL_UNTRUSTED.

After installing aia_chaser: ^0.1.0:

  • On cold boot, both acrex.in and register.acrex.in were prewarmed in the background.
  • Subsequent API requests and WebViews reported:
  [AiaChaser] Prewarmed acrex.in -> ValidationOutcome.validated (3ms - In-memory cache hit)
  [AiaChaser] Prewarmed register.acrex.in -> ValidationOutcome.validated (1ms - In-memory cache hit)
  <-- 200 https://register.acrex.in/ACREXService.svc/GetSliderList
  <-- 200 https://register.acrex.in/ACREXService.svc/SearchExhibitorNew (254 items loaded)
  ✅ [WEBVIEW PAGE FINISHED] Loaded URL: https://register.acrex.in/hallLayout/...
Enter fullscreen mode Exit fullscreen mode
  • Zero handshake exceptions, zero insecure bypasses, and 100% data fidelity.

Built-In Diagnostics Widget

Need to inspect what certificates were chased or verify cache hits? AIA Chaser includes a Flutter Material 3 diagnostic inspector:

Navigator.of(context).push(
  MaterialPageRoute(
    builder: (_) => const AiaDiagnosticsView(),
  ),
);
Enter fullscreen mode Exit fullscreen mode

It gives you:

  • Visual host list with green/red status indicators.
  • Cache hit/miss counts and probe latency (in milliseconds).
  • Subject, Issuer, Serial Number, and SHA-256 fingerprint of all intercepted certs.
  • "Flush Cache" and "Prewarm All" actions for real-time testing.

Conclusion & Community Wisdom

Missing intermediate certificates are one of the most frustrating pain points in mobile development because desktop browsers hide the problem, leaving mobile developers feeling like it's a bug in Flutter or Android.

Bypassing verification with badCertificateCallback = (...) => true is never the answer.

With AIA Chaser, you get desktop-grade TLS resilience while maintaining strict cryptographic security.

Give it a star on GitHub, try it in your projects, and never let an incomplete SSL certificate chain take your mobile app down again!

Top comments (0)