secure_pinning 0.0.2 copy "secure_pinning: ^0.0.2" to clipboard
secure_pinning: ^0.0.2 copied to clipboard

Certificate pinning for Flutter with SPKI pinning by default, a pure-Dart validation engine, and typed exceptions across every platform.

secure_pinning #

Certificate pinning for Flutter. package:http/Dio/raw-HttpClient pinning works anywhere dart:io's TLS stack runs — Android, iOS, macOS, Windows, and Linux. The native SecurePinning.check() probe API is available on Android, iOS, and macOS. Web is permanently unsupported for everything (browsers never expose TLS certificate bytes to page JavaScript).

Why another certificate-pinning plugin? #

Most Flutter HTTP traffic (package:http's default client, Dio's default adapter) runs on dart:io's own TLS stack, not on OkHttp or NSURLSession. secure_pinning's core mechanism validates the actual connection your request uses — no separate preflight connection, no double round-trip — via dart:io's HttpClient.badCertificateCallback. See docs/SECURITY_MODEL.md for the full design rationale, SPKI vs. leaf vs. CA pinning tradeoffs, and what certificate pinning does and doesn't protect against.

Features #

  • Three pinning modesPinningMode.spki (public-key hash, survives certificate renewal, the default), PinningMode.legacyLeafHash (whole-certificate hash, for compatibility with plugins that pin the full leaf certificate instead of just its public key), and PinningMode.legacyCaHash (CA/root pinning, gated behind a required acknowledgedRisk justification).
  • Validates the real connection — the pure-Dart engine hooks dart:io's HttpClient.badCertificateCallback directly, so package:http and Dio traffic is checked on the actual request with no separate preflight round trip.
  • SHA-256 and SHA-1 hash algorithm support (HashAlgorithm).
  • Backup-pin support — list multiple pins so a future key rotation doesn't require an app update.
  • Configurable connect/read timeouts, enforced even where dart:io has no native read-timeout concept.
  • A typed exception hierarchySecurePinningValidationException, SecurePinningTimeoutException, SecurePinningConfigurationException, SecurePinningUnsupportedPlatformException, SecurePinningNetworkException — consistent across every platform and integration surface, never a raw HandshakeException/PlatformException.
  • package:http and Dio integrations (secure_pinning_http, secure_pinning_dio) as separate opt-in packages — pull in only what you use.
  • A native probe API (SecurePinning.check() + SecurePinning.isPlatformSupported()) for one-off/out-of-band checks, and the only path that can validate PinningMode.legacyCaHash (which requires walking the full certificate chain). Available on Android, iOS, and macOS.
  • Federated plugin architecture — the core secure_pinning package has zero third-party dependencies beyond crypto; native platform code lives in its own package per platform.
  • Platform coveragepackage:http/Dio/raw-HttpClient pinning works on Android, iOS, macOS, Windows, and Linux (anywhere dart:io's TLS stack runs); the native probe API is Android/iOS/macOS only; Web is permanently unsupported for everything (browsers never expose TLS certificate bytes to page JavaScript).

Packages #

Package Purpose
secure_pinning (this repo's root package) Core: config, validation engine, raw HttpClient factory, typed exceptions, native probe API. Zero third-party dependencies.
secure_pinning_http package:http-compatible BaseClient wrapper.
secure_pinning_dio Dio interceptor-shaped integration.
secure_pinning_platform_interface Pigeon-generated platform channel schema.
secure_pinning_android Android (Kotlin/javax.net.ssl) native probe engine.
secure_pinning_apple iOS + macOS (Swift/Security.framework) native probe engine, one shared package.

Windows, Linux, and Web don't have a secure_pinning_* platform package — package:http/Dio/raw-HttpClient pinning still works on Windows and Linux regardless (it's pure-Dart, not gated by a platform package at all), but calling the native SecurePinning.check() probe on those two, or on Web, isn't supported. See Platform coverage above.

Installation #

Add whichever integration package(s) match your HTTP stack — you don't need all of them:

flutter pub add secure_pinning_http   # package:http
flutter pub add secure_pinning_dio    # Dio
flutter pub add secure_pinning        # raw HttpClient / native probe only, no HTTP client wrapper

secure_pinning_http and secure_pinning_dio both depend on secure_pinning automatically — you don't need to add it yourself unless you're using the raw client or the native probe API directly.

Getting your pins #

Every usage example below needs at least one SPKI pin for the host you're pinning, as a hex string (colon-tolerant, case-insensitive — not base64). Compute it with:

openssl s_client -connect HOST:443 -servername HOST < /dev/null 2>/dev/null \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -hex \
  | awk '{print $NF}'

Always include a second, backup pin (e.g. the SPKI hash of the issuing CA, or a planned replacement key) — a single pin means any future key rotation on the server locks your app out until an update ships.

Usage #

The easiest path for most apps is secure_pinning_http or secure_pinning_dio — pick whichever matches your existing HTTP stack. Both are thin wrappers with one required call to get pinning working.

package:http #

import 'package:secure_pinning_http/secure_pinning_http.dart';

final client = SecurePinningHttpClient.forHost(
  'api.example.com',
  pins: ['hex-spki-hash-1', 'hex-spki-hash-2'], // include a backup pin
);
final response = await client.get(Uri.https('api.example.com', '/'));

SecurePinningHttpClient is a full http.BaseClientget, post, put, patch, delete, head, read, readBytes, and streaming via send() all work as usual, validated against the pinned connection.

Dio #

import 'package:dio/dio.dart';
import 'package:secure_pinning_dio/secure_pinning_dio.dart';

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
dio.interceptors.add(
  SecurePinningInterceptor.forHost(
    dio,
    'api.example.com',
    pins: ['hex-spki-hash-1', 'hex-spki-hash-2'],
  ),
);

final response = await dio.get('/');

Add SecurePinningInterceptor before making any request on dio — it installs a pinned IOHttpClientAdapter at construction time.

Raw HttpClient / a custom stack #

If you're not using package:http or Dio, SecurePinning.createHttpClient gives you a pinned dart:io HttpClient directly:

import 'package:secure_pinning/secure_pinning.dart';

final config = SecurePinningConfig(
  host: 'api.example.com',
  pins: ['hex-spki-hash-1', 'hex-spki-hash-2'],
);
final client = SecurePinning.createHttpClient(config);

final request = await client.getUrl(Uri.https('api.example.com', '/'));
final response = await SecurePinning.enforceReadTimeout(
  request.close(),
  config.readTimeout,
);

enforceReadTimeout is needed because dart:io's HttpClient has no built-in read-timeout concept the way OkHttp/URLSession do — both integration packages above already call it internally.

One-off checks and CA/root pinning #

SecurePinning.check() validates a pin set against a URL independently of any HTTP request — useful for a startup connectivity check, or as the only way to validate PinningMode.legacyCaHash (CA-level pinning requires walking the full certificate chain, which the pure-Dart engine above can't do):

import 'package:secure_pinning/secure_pinning.dart';

final result = await SecurePinning.check(
  url: 'https://api.example.com',
  config: SecurePinningConfig(
    host: 'api.example.com',
    pins: ['hex-spki-hash-1', 'hex-spki-hash-2'],
  ),
);
print(result.isTrusted ? 'Trusted' : 'Rejected: ${result.errorCode}');

A pin mismatch here is returned as data (result.isTrusted == false), not thrown — only genuine infrastructure failures (DNS failure, malformed config) throw. Check SecurePinning.isPlatformSupported() first if you need to know whether it's available — Android, iOS, and macOS only; unlike package:http/Dio/raw-HttpClient pinning, this probe doesn't work on Windows or Linux.

Handling failures #

All three integrations above throw the same typed exception hierarchy on a pinning or connectivity failure — catch SecurePinningException (or a specific subtype) instead of a raw HandshakeException/SocketException:

try {
  final response = await client.get(Uri.https('api.example.com', '/'));
} on SecurePinningValidationException catch (e) {
  // The presented certificate didn't match the configured pins.
} on SecurePinningTimeoutException catch (e) {
  // Connect or read timed out.
} on SecurePinningException catch (e) {
  // Any other pinning-related failure — see e.message.
}

See each package's README for its full API, and example for a runnable app covering the raw client, package:http, Dio, and native probe integrations side by side.

Using the example app #

  1. cd example && flutter pub get
  2. flutter run and pick a connected device or simulator.
  3. Enter a Host and Pins — the shipped values are placeholders that won't validate; see example/README.md for the openssl one-liner to compute real SPKI pins for your own host.
  4. Pick a pinning mode from the segmented selector (SPKI, Legacy leaf hash, or Legacy CA hash — selecting CA hash reveals a required "acknowledged risk" field).
  5. Tap Raw HttpClient, package:http, Dio, or Native probe (check()) to run a pinned request through that integration surface. Results (or the typed exception thrown) appear in the log below — note that CA hash only actually validates through the native probe button; the other three correctly reject it with SecurePinningConfigurationException, since the pure-Dart engine can't walk the full certificate chain that CA-level pinning requires.

License #

Apache-2.0 — see LICENSE.

0
likes
160
points
108
downloads

Documentation

API reference

Publisher

verified publishertherivanta.com

Weekly Downloads

Certificate pinning for Flutter with SPKI pinning by default, a pure-Dart validation engine, and typed exceptions across every platform.

Repository (GitHub)
View/report issues
Contributing

Topics

#security #networking #certificate-pinning #tls #http

License

Apache-2.0 (license)

Dependencies

crypto, flutter, secure_pinning_android, secure_pinning_apple, secure_pinning_platform_interface

More

Packages that depend on secure_pinning