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)
Or if you're loading an in-app WebView with webview_flutter:
onReceivedSslError: primaryError: SSL_UNTRUSTED (net::ERR_CERT_AUTHORITY_INVALID)
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;
}
}
Or in your WebView:
// ⚠️ INSTANT SECURITY HOLE:
onReceivedSslError: (controller, error) {
handler.proceed(); // Blindly accepts any certificate
}
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:
- A server sends only its leaf certificate (
cert.pem), omitting the intermediate certificate (chain.pem). - 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):
- Chrome parses the leaf certificate's X.509 extensions and finds the
caIssuersURI (e.g.,http://crt.sectigo.com/SectigoRSADomainValidationSecureServerCA.crt). - Chrome quietly makes an HTTP
GETrequest in the background to download the missing intermediate certificate. - 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.
Key Principles
- 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.
-
Host-Scoped Policies: You explicitly define which hostnames or wildcards (
*.example.com) are allowed to chase certificates. - 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.
- 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.
-
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
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());
}
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);
}
If the server sends a partial chain, AiaWebViewHandler will:
- Intercept
SSL_UNTRUSTED. - Extract the leaf cert and chase the intermediate.
- Validate against Android's system trust store (
X509TrustManagerExtensions). - Only call
proceed()if the chain is cryptographically verified. - 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.inandregister.acrex.inwere 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/...
- 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(),
),
);
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.
- GitHub Repository: https://github.com/YadneshTeli/aia_chaser
- Pub.dev Package: https://pub.dev/packages/aia_chaser
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)