Flutter证书锁定网络请求插件certificate_pinning_httpclient的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter证书锁定网络请求插件 certificate_pinning_httpclient 的使用

certificate_pinning_httpclient 是一个实现了带有证书锁定功能的 HttpClient 插件。锁定是基于 SPKI(主题公钥信息)SHA-256 哈希值进行的。客户端将通过 MethodChannel 下载证书并缓存它们,所有与 SPKI 哈希匹配的证书将用于 SecurityContext。

使用方法

获取 SPKI 哈希值

客户端会记录每个证书链中的 SPKI 哈希值。你可以使用这些哈希值来配置你的应用。你也可以通过 GnuTLS 来获取哈希值:gnutls-cli --print-cert example.com (查找 Public Key PIN)。

示例代码

使用 http

import 'package:certificate_pinning_httpclient/certificate_pinning_httpclient.dart';
import 'package:http/io_client.dart';

final client = IOClient(CertificatePinningHttpClient(
    ["S4kZuhQQ1DPcMOCYFQXD0gG+UW0zmyVx6roNWpRl65I="]));

使用 Dio

import 'package:certificate_pinning_httpclient/certificate_pinning_httpclient.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';

final _dio = Dio();
(_dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
    (client) => CertificatePinningHttpClient(
        ["S4kZuhQQ1DPcMOCYFQXD0gG+UW0zmyVx6roNWpRl65I="]);

禁用日志(适用于发布版本)

import 'package:logger/logger.dart';

Logger.level = kDebugMode ? Level.debug : Level.nothing;

完整示例 Demo

以下是一个完整的 Flutter 应用示例,展示了如何使用 certificate_pinning_httpclient 插件:

import 'package:certificate_pinning_httpclient/certificate_pinning_httpclient.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/material.dart';
import 'package:http/io_client.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Certificate Pinning Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _httpStatus = 0;
  int _dioStatus = 0;
  String _httpError = "";
  String _dioError = "";
  static const String exampleComSpki =
      "Xs+pjRp23QkmXeH31KEAjM1aWvxpHT6vYy+q2ltqtaM="; // 可能过期

  final _http = IOClient(CertificatePinningHttpClient([exampleComSpki]));
  final _dio = Dio();

  _MyHomePageState() {
    (_dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
        (client) => CertificatePinningHttpClient([exampleComSpki]);
  }

  Future<void> _makeHttpCall(String url) async {
    int httpStatus = -1;
    int dioStatus = -1;
    String? httpError;
    String? dioError;

    try {
      httpStatus = (await _http.get(Uri.parse(url))).statusCode;
    } catch (err) {
      httpError = err.toString();
    }

    try {
      dioStatus = (await _dio.get(url)).statusCode!;
    } catch (err) {
      dioError = err.toString();
    }

    setState(() {
      _httpStatus = httpStatus;
      _dioStatus = dioStatus;
      _httpError = httpError ?? "";
      _dioError = dioError ?? "";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(
              onPressed: () {
                _makeHttpCall("https://example.com");
              },
              child: const Text("GET example.com"),
            ),
            TextButton(
              onPressed: () {
                _makeHttpCall("https://pub.dev");
              },
              child: const Text("GET pub.dev (with invalid pins)"),
            ),
            const SizedBox(height: 20),
            Text('Http status from http client: $_httpStatus'),
            Text('Http status from dio client: $_dioStatus'),
            const SizedBox(height: 20),
            if (_httpError.isNotEmpty)
              Text("Error from http client: $_httpError"),
            const SizedBox(height: 20),
            if (_dioError.isNotEmpty) Text("Error from dio client: $_dioError"),
          ],
        ),
      ),
    );
  }
}

致谢

感谢 Approov 提供的基础工作。


更多关于Flutter证书锁定网络请求插件certificate_pinning_httpclient的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter证书锁定网络请求插件certificate_pinning_httpclient的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用certificate_pinning_httpclient插件来实现证书锁定的示例代码。certificate_pinning_httpclient插件允许你通过预定义的证书来实现HTTPS请求的证书锁定,以提高应用的安全性。

步骤 1: 添加依赖

首先,你需要在pubspec.yaml文件中添加certificate_pinning_httpclient依赖:

dependencies:
  flutter:
    sdk: flutter
  certificate_pinning_httpclient: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

步骤 2: 配置证书

将你需要锁定的证书(通常是.pem格式)放置在你的Flutter项目的assets目录下。确保在pubspec.yaml中声明这些资产:

flutter:
  assets:
    - assets/your_certificate.pem

步骤 3: 使用插件进行网络请求

以下是一个完整的示例,展示了如何使用certificate_pinning_httpclient插件进行网络请求:

import 'package:flutter/material.dart';
import 'package:certificate_pinning_httpclient/certificate_pinning_httpclient.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Certificate Pinning Example'),
        ),
        body: Center(
          child: CertificatePinningExample(),
        ),
      ),
    );
  }
}

class CertificatePinningExample extends StatefulWidget {
  @override
  _CertificatePinningExampleState createState() => _CertificatePinningExampleState();
}

class _CertificatePinningExampleState extends State<CertificatePinningExample> {
  String responseData = '';

  @override
  void initState() {
    super.initState();
    _fetchData();
  }

  Future<void> _fetchData() async {
    try {
      // 创建CertificatePinningHttpClient实例
      final client = CertificatePinningHttpClient.create(
        pinnedCertificates: [
          'assets/your_certificate.pem'  // 替换为你的证书路径
        ],
        trustAllCertificates: false,  // 不信任所有证书,只信任列出的证书
      );

      // 使用创建的客户端进行GET请求
      final response = await client.get(Uri.parse('https://your-api-endpoint.com/data'));

      // 检查响应状态并读取数据
      if (response.statusCode == 200) {
        setState(() {
          responseData = response.body;
        });
      } else {
        setState(() {
          responseData = 'Failed to fetch data: ${response.statusCode}';
        });
      }
    } catch (e) {
      setState(() {
        responseData = 'Error: $e';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Text(responseData);
  }
}

注意事项

  1. 证书路径:确保pinnedCertificates中的证书路径正确无误。
  2. 错误处理:在真实应用中,应添加更多的错误处理逻辑,例如重试机制、超时处理等。
  3. 安全性:不要在生产环境中使用trustAllCertificates: true,这会绕过所有证书验证,降低安全性。

通过上述步骤,你应该能够在Flutter项目中成功使用certificate_pinning_httpclient插件来实现证书锁定的网络请求。

回到顶部