git / moonbite-network / mobile/lib/services/cert_pinning.dart · raw

import 'dart:convert';import 'dart:io';import 'package:http/http.dart' as http;import 'package:http/io_client.dart';/// TLS certificate pinning for the explorer connection.////// The wallet's keys never leave the device, but a man-in-the-middle on the/// explorer link could still feed the wallet false chain state (fake balances //// UTXOs) or observe the user's addresses. Ordinary HTTPS trusts every CA in/// the OS store, so a single rogue or coerced CA — or a locally-installed/// interception root — is enough to MITM.////// We pin to Let's Encrypt's ISRG Root X2, the root the live endpoint/// (moonbite.org, see ChainService.defaultBaseUrl) chains to. Pinning the *root* (not the/// short-lived leaf) survives normal 90-day leaf/intermediate renewals while/// still rejecting any certificate issued by a different CA.////// SPKI SHA-256 pin (base64) of the key below, for auditing:///   diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=/// Verified against the live TLS handshake. Valid until 2032-09-02.////// OPERATIONAL NOTE: if the endpoint's CA chain ever changes (e.g. the host/// migrates off Let's Encrypt), this root must be updated and the app/// re-released, otherwise every request will fail closed. That availability/// cost is the deliberate price of pinning.const String kIsrgRootX2SpkiPin =    'diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=';const String _isrgRootX2Pem = '''-----BEGIN CERTIFICATE-----MIIEcDCCAligAwIBAgIQbI8dxyfHEX97r4U6yYD5zTANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNjA1MTMwMDAwMDBaFw0zMjA5MDIyMzU5NTlaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppbd9/w+kHsOdx1ymgHDB/qo4H1MIHyMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwHwYDVR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcGA1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcNAQELBQADggIBAD2/e9frmMxNpCV03qUHegg+MV2wz9644YoXdqtH8RyWYcBO7xfjjGEXdU1e/o0OkEFiynUCOSIk/vLLo7ttz6CPAeNlWfC0XNkoGeWgK6jjXvozBaGuGH5n0UfoshMeWTuURqNN5G00sSXDTBrpp2+mgvdZQjb8K11TYMA25QA+YHNfbIEL0BniAhKS2gsnJjSzrdZLI+EZ7SEyqdR2rkjd1KutLDU+n3TFyxjniZVGur4YlhMP3mY/dV95IruAkkjOZier6hGBdEgZXXvaCz9u9iVEadsIE75pAGL8oHV5vxdARDiotRpul1IN/UZwzAbrfUFcw1HkAcYD/mlZfnQ2ieCF2MS7j3Vhv7JPDKp45fmykmzYNSrumRW0upFFKDBOoF7hsOb7oLyHS+Uft6jOUfOrogj8YUx38hKb2K20r42OgsSdDdxdeYWcMS3Sb6mwJeSZEYxJ2gaXnDSPaKhhrNkYwljyVQyr4Nq+MEJytXNTnHqaAcrNwZlVpcJL1KBnMrMjP7eanvUwL3FYj3cF17jtboLt7gLoi4+2rWZFvn+w54jmd/FIuhhZcEaU/wvU6BUNMtcVquVGHp7itQeDth5j+XL3j4WJ2SABwzUl6OeYdgpIt/ITZa+pTT0mQ/r5XyA4MEAiabn7XJjvCERlF2dcn2wqJw+CreTkkQ2R-----END CERTIFICATE-----''';/// Builds an [http.Client] that only trusts certificates chaining to the pinned/// ISRG root. [withTrustedRoots] is false, so the OS trust store is ignored and/// a MITM certificate from any other CA fails the handshake. A cert that fails/// validation is rejected outright (badCertificateCallback returns false).http.Client createPinnedHttpClient() {  final context = SecurityContext(withTrustedRoots: false)    ..setTrustedCertificatesBytes(utf8.encode(_isrgRootX2Pem));  final inner = HttpClient(context: context)    ..badCertificateCallback = (cert, host, port) => false;  return IOClient(inner);}