flutter 中 ssl 双向证书校验

SSL 证书:

在处理 https 请求的时候,通常可以使用 中间人攻击的方式 获取 https 请求以及响应参数 。应为通常我们是 SSL 单向认证,服务器并没有验证我们的客户端的证书 。为了防止这种中间人攻击的情况。我么可以通过 ssl 双向认证的方式。即服务端要验证客户端请求的证书合法性。

SSL 单向认证

无需客户端拥有证书,只需服务端拥有证书 客户端也需要验证服务端证书;SSL 单向认证相对于 SSL 双向认证的认证过程,无需在服务端验证客户端证书、以及协商加密方案,服务端发送给客户端也是未加密的密码方案(并不影响 SSL 认证过程的安全性)

SSL 双向认证

需要客户端 和 服务端双方都拥有证书。并且验证是双向的,即服务端 需要验证 客户端证书,客户端也需要验证服务端证书。当然服务器 也需要开启认证 客户端证书的操作并且证书如果放在客户端 如何保证证书的安全性也是一个****很好的话题,否则意义不会太大

生成 客户端 ssl 证书

因为 Flutter 不支持 .p12 格式的,这里需要生成 .pem 格式的证书

1. 准备环境

首先,确保你已经安装了 OpenSSL 工具。OpenSSL 是处理证书的常用工具,可以在大多数操作系统上安装。

2. 生成客户端证书密钥对

客户端证书需要一个公私钥对。首先,你需要生成一个私钥文件 过程需要输入秘钥

openssl genpkey -algorithm RSA -out client_key.pem -aes256

# client_key.pem 是你生成的私钥文件。-aes256 参数表示私钥文件将使用 AES-256 加密。
3. 创建客户端证书签名请求 (CSR)

使用生成的私钥来创建一个客户端证书签名请求(CSR):

openssl req -new -key client_key.pem -out client_csr.pem

#在执行这个命令时,你需要提供一些信息,例如国家、州、城市、组织名等。这些信息将被包含在 CSR 中。
4. 签署客户端证书
选项 1: 使用自签名证书

如果你使用自签名证书(即没有使用正式的 CA),可以通过 OpenSSL 自行签署客户端证书:

首先,生成一个自签名证书:

openssl x509 -req -in client_csr.pem -signkey client_key.pem -out client_cert.pem

##### 这个证书是自签名的,仅在你的环境中有效,不适用于生产环境。
选项 2: 通过服务端证书签署

如果你希望使用服务端证书签署客户端证书通常服务端证书由一个 CA 签发 即你服务器SSL 证书提供商,你需要以下步骤:

  • 将客户端 CSR 提交给 CA : 将 client_csr.pem 提交给负责签署客户端证书的 CA。CA 将使用其私钥来签署客户端 CSR,生成一个客户端证书。

  • CA 签署客户端证书 : CA 使用它的私钥对 CSR 进行签名,生成客户端证书。假设 CA 使用 ca-cert.pemca-key.pem 文件签署证书,你可以使用如下命令

    openssl x509 -req -in client_csr.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client_cert.pem -days 365

    ##这里,ca-cert.pem 是 CA 的证书,ca-key.pem 是 CA 的私钥,-CAcreateserial 参数表示如果没有 CA 序列号文件,则创建一个。

Flutter 中使用客户端 ssl 证书

如果是全局配置:main.dart 中

HttpOverrides.global = await AppHttpCert().createHttpProxy();

核心代理就这里:

///证书校验,Flutter  暂不支持 .p12
@override
HttpClient createHttpClient(SecurityContext? context) {
  // TODO: implement createHttpClient
  //return super.createHttpClient(context);

  final securityContext = SecurityContext(withTrustedRoots: false)
    ..useCertificateChainBytes(clientCert.buffer.asUint8List(),
        password: 'https://pan.baidu.com/j/1WtKrUBVVZS')
    ..usePrivateKeyBytes(clientKey.buffer.asUint8List(),
        password: 'https://pan.baidu.com/j/1WtKrUBVVZS');


  HttpClient client = super.createHttpClient(securityContext);
  client.badCertificateCallback = _badCertificateCallback;

  return client;
}

///用于处理不受信任的证书。通过这个回调函数,你可以根据自定义逻辑决定是否接受一个不受信任的证书
bool _badCertificateCallback(X509Certificate cert, String host, int port) {
  // 打印证书信息
  print('Received certificate from host: $host, port: $port');
  print('Certificate subject: ${cert.subject}');
  print('Certificate issuer: ${cert.issuer}');
  print('Certificate valid from: ${cert.startValidity}');
  print('Certificate valid until: ${cert.endValidity}');

  // return true;

  if (kReleaseMode) {
    return false;
  } else {
    return true;
  }
}

完整代码:因为我这里在开发阶段使用代理抓包,不需要的可以直接略过

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

class AppHttpCert {
  String? host;
  String? port;

  MethodChannel _channel = MethodChannel('com.lm.http.proxy');

  Future<String?> _getProxyHost() async {
    return await _channel.invokeMethod('getProxyHost');
  }

  Future<String?> _getProxyPort() async {
    return await _channel.invokeMethod('getProxyPort');
  }

  Future<AppHttpOverrides> createHttpProxy() async {
    final clientCert = await rootBundle.load('assets/client_cert.pem');
    final clientKey = await rootBundle.load('assets/client_key.pem');

    final String? host = await _getProxyHost();
    final String? port = await _getProxyPort();

    return AppHttpOverrides(
      clientCert: clientCert,
      clientKey: clientKey,
      host: host,
      port: port,
    );
  }
}

class AppHttpOverrides extends HttpOverrides {
  final String? host;
  final String? port;
  final ByteData clientCert;
  final ByteData clientKey;
  AppHttpOverrides(
      {required this.clientCert,
      required this.clientKey,
      required this.host,
      required this.port});

  ///证书校验,暂不支持 .p12
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    // TODO: implement createHttpClient
    //return super.createHttpClient(context);

    final securityContext = SecurityContext(withTrustedRoots: false)
      ..useCertificateChainBytes(clientCert.buffer.asUint8List(),
          password: 'https://pan.baidu.com/j/1WtKrUBVVZS')
      ..usePrivateKeyBytes(clientKey.buffer.asUint8List(),
          password: 'https://pan.baidu.com/j/1WtKrUBVVZS');

    // // 设置受信任的服务器证书
    // securityContext
    //     .setTrustedCertificatesBytes(serverCert.buffer.asUint8List());

    HttpClient client = super.createHttpClient(securityContext);
    client.badCertificateCallback = _badCertificateCallback;

    return client;
  }

  ///用于处理不受信任的证书。通过这个回调函数,你可以根据自定义逻辑决定是否接受一个不受信任的证书
  bool _badCertificateCallback(X509Certificate cert, String host, int port) {
    // 打印证书信息
    print('Received certificate from host: $host, port: $port');
    print('Certificate subject: ${cert.subject}');
    print('Certificate issuer: ${cert.issuer}');
    print('Certificate valid from: ${cert.startValidity}');
    print('Certificate valid until: ${cert.endValidity}');

    // return true;

    if (kReleaseMode) {
      return false;
    } else {
      return true;
    }
  }

  ///是否使用代理
  @override
  String findProxyFromEnvironment(Uri url, Map<String, String>? environment) {
    // TODO: implement findProxyFromEnvironment
    if (host == null) {
      return super.findProxyFromEnvironment(url, environment);
    }
    environment ??= {};
    if (port != null) {
      environment['http_proxy'] = '$host:$port';
      environment['https_proxy'] = '$host:$port';
    } else {
      environment['http_proxy'] = '$host:8888';
      environment['https_proxy'] = '$host:8888';
    }
    return super.findProxyFromEnvironment(url, environment);
  }
}
相关推荐
tRNA做科研33 分钟前
Bio-Linux-shell详解-1-从0开始
linux·运维·服务器
KookeeyLena71 小时前
udp协议除了打游戏还有什么用途
网络·网络协议·udp
kaixin_啊啊1 小时前
TCP/IP模型成功与OSI模型失败的深层原因:技术、理念与市场化路径的比较
服务器·tcp/ip·php
gs801402 小时前
安装node 报错需要:glibc >= 2.28
linux·服务器·前端·node.js
hgdlip2 小时前
一台电脑对应一个IP地址吗?‌探讨两台电脑共用IP的可能性
服务器·网络协议·tcp/ip·电脑
懒人w4 小时前
WebSocket 和 HTTP 请求区别
websocket·网络协议·http
HelloTonyGo4 小时前
QT5实现https的post请求(QNetworkAccessManager、QNetworkRequest和QNetworkReply)
https·wireshark·ssl·post·qt5
alsknv4 小时前
IP地址是怎么实现HTTPS访问的?
网络·网络协议·tcp/ip·安全·web安全·http·https
GDAL4 小时前
Puppeteer-Cluster:并行处理网页操作的新利器
运维·服务器·nodehtmltoimage
limengshi1383925 小时前
通信工程学习:什么是GFP通用成帧规范
服务器·网络·网络协议·学习·信息与通信