Flutter ACME客户端插件acme_client的使用

Flutter ACME客户端插件acme_client的使用

ACME客户端插件 acme_client 是一个基于 Dart 编写的 ACME V2 兼容客户端。该插件可以用于所有支持 Dart 的平台上,包括 Flutter、Angular Dart 等。

目录

前言

由于该包是纯 Dart 编写的,因此可以在任何支持 Dart 的平台上使用,包括 Flutter 框架。你还可以将该包用于命令行工具或通过 dart2native 编译的 REST 服务。

注意: 如果你想贡献代码,可以通过创建拉取请求或者报告问题来提交 bug、问题和功能需求。

安装

pubspec.yaml

pubspec.yaml 文件中更新依赖项,并添加以下行:

dependencies:
  acme_client: ^1.3.0

导入

在 Dart 文件中导入插件:

import 'package:acme_client/acme_client.dart';

Acme 客户端

这是一个基于 RFC 8555 的简单 ACME 客户端。它应该能够与所有基于该 RFC 的 ACME 服务器通信,包括 Let’s Encrypt。

客户端设置

创建一个新的客户端实例并传递适当的参数:

var client = AcmeClient(
  'https://acme-server.com',
  privateKeyPem,
  publicKeyPem,
  true,
  ['mailto:jon@doe.com'],
);

参数说明:

  • baseUrl: ACME 服务器的基础 URL。
  • privateKeyPem: 私钥(PEM 格式)。
  • publicKeyPem: 公钥(PEM 格式)。
  • acceptTerms: 是否接受条款和条件。
  • contacts: 邮箱地址列表,每个地址格式为 mailto:jon@doe.com

注意: 如果你想用 Dart 创建 RSA/ECC 密钥对,可以查看 Basic Utils 包。其中包含创建密钥对所需的工具和格式化为 PEM 的方法。

设置好客户端后,调用 init() 方法以从服务器获取目录和账户信息。如果给定的公钥在服务器上没有账户,客户端将创建一个新的账户。

await client.init();

申请证书颁发

下单

可以通过创建一个新的订单对象并添加要放入证书的标识符来下单。然后,客户端的方法将返回由 ACME 服务器返回的订单信息。

var order = Order(
  identifiers: [Identifiers(type: 'dns', value: 'example.com')]
);
var newOrder = await client.order(order);

获取授权数据

对于每个订单,ACME 服务器会返回每个标识符的授权数据。你可以通过 getAuthorization() 方法获取这些数据。

var auth = await client.getAuthorization(newOrder!);

获取授权挑战

对于每个返回的授权,有多个挑战。你可以使用其中一个挑战来证明对一个标识符的控制权并完成授权请求。

for(var a in auth){
   var data = a.getHttpDcvData();
}

for(var a in auth){
   var data = a.getDnsDcvData();
}

自测

建议在使用适当客户端方法之前先检查挑战是否通过。通过 maxAttempts 参数可以增加或减少尝试检查挑战令牌的时间。默认值为 15。

注意: DNS 自测使用 Google DNS Rest API 来获取资源记录。

var self = await client.selfDNSTest(data); // DnsDcvData
if (!self) {
  print('Selftest failed, no DNS record found');
}

var self = await client.selfHttpTest(data); // HttpDcvData
if (!self) {
  print('Selftest failed, no file found or content missmatch');
}

触发验证

为了告诉 ACME 服务器检查挑战,可以使用 validate() 方法传递所需的挑战。这将触发验证,并每 4 秒检查一次授权状态,直到变为 “valid”。通过 maxAttempts 参数可以增加或减少轮询状态的时间。默认值为 15。

var data; // HttpDcvData or DnsDcvData
var authValid = await client.validate(data.challenge);
if (!authValid) {
  print('Authorization failed, exit');
}

完成订单

如果每个授权的状态都是 “valid”,则通过发送 CSR 到 ACME 服务器来完成订单。CSR 将根据 RFC 规则自动格式化(Base64Url 编码且不带头部)。

var finalOrder = await client.finalizeOrder(newOrder, csr);

获取证书

可以通过传递已完成的订单对象来获取证书列表。

var certs = await client.getCertificate(finalOrder);

完整示例代码

以下是完整的示例代码,展示了如何使用 acme_client 插件申请证书颁发。

import 'dart:convert';
import 'dart:io';

import 'package:acme_client/src/acme_client.dart';
import 'package:acme_client/src/constants.dart';
import 'package:acme_client/src/model/identifiers.dart';
import 'package:acme_client/src/model/order.dart';
import 'package:basic_utils/basic_utils.dart';

void main(List<String> args) async {
  var privateKeyPem = '''
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
''';

  var publicKeyPem = '''
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
''';

  var csr = '''
-----BEGIN CERTIFICATE REQUEST-----
...
-----END CERTIFICATE REQUEST-----
''';

  var cn = 'foobar.com';

  var client = AcmeClient(
    'https://acme-staging-v02.api.letsencrypt.org',
    privateKeyPem,
    publicKeyPem,
    true,
    ['mailto:jon@doe.com'],
  );
  await client.init();

  var order = Order();
  var identifier = Identifiers(type: 'dns', value: cn);
  order.identifiers = [identifier];
  print('Order certificate for $cn');
  var newOrder = await client.order(order);

  print('Fetch authorization data for order');
  var auth = await client.getAuthorization(newOrder!);
  print('Place the following DNS record in the corresponding zone file:');
  print(DnsUtils.toBind(auth!.first.getDnsDcvData().rRecord));
  print('Press any key if you are ready to trigger the challenge check');
  stdin.readLineSync(encoding: utf8);

  var self = await client.selfDNSTest(auth.first.getDnsDcvData());
  if (!self) {
    print('Selftest failed, no DNS record found');
    exit(0);
  }

  var authValid = await client.validate(auth.first.challenges!
      .firstWhere((element) => element.type == VALIDATION_DNS));

  if (!authValid) {
    print('Authorization failed, exit');
    exit(0);
  }
  print('Authorization successfull, finalize order');
  await Future.delayed(Duration(seconds: 1));
  var ready = await client.isReady(newOrder);
  if (!ready) {
    print('Order is not ready ...');
    exit(0);
  }
  print('Order is ready, finalize order');

  var persistent = await client.finalizeOrder(newOrder, csr);

  var certs = await client.getCertificate(persistent!);
  print(certs);
}

更多关于Flutter ACME客户端插件acme_client的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter ACME客户端插件acme_client的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用acme_client插件的示例代码案例。acme_client插件通常用于与ACME(Automated Certificate Management Environment)协议交互,以自动化SSL/TLS证书的获取和管理。这在实现HTTPS时非常有用。

请注意,实际使用中你可能需要根据ACME服务器的具体要求(如Let’s Encrypt)调整代码。此外,由于acme_client插件的具体实现和API可能会随时间变化,以下代码仅供参考,并可能需要根据你使用的插件版本进行调整。

首先,确保你的pubspec.yaml文件中已经添加了acme_client依赖:

dependencies:
  flutter:
    sdk: flutter
  acme_client: ^最新版本号  # 替换为实际可用的最新版本号

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

接下来是一个简单的Flutter应用示例,演示如何使用acme_client插件:

import 'package:flutter/material.dart';
import 'package:acme_client/acme_client.dart';
import 'dart:convert';
import 'dart:io';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _certificateStatus = 'Fetching certificate...';

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

  Future<void> _fetchCertificate() async {
    try {
      // 配置ACME客户端
      final acmeClient = AcmeClient(
        directoryUrl: Uri.parse('https://acme-v02.api.letsencrypt.org/directory'), // Let's Encrypt的目录URL
        email: 'your-email@example.com', // 你的邮箱地址,用于接收证书到期通知
        // 私钥和注册信息,这里假设你已经有了私钥和注册好的账户信息
        privateKey: File('path/to/your/private/key.pem').readAsBytesSync(),
        registration: Registration.fromJson(jsonDecode(File('path/to/your/registration.json').readAsStringSync())),
      );

      // 获取证书
      final order = await acmeClient.newOrder(identifiers: [Identifier(type: 'dns', value: 'yourdomain.com')]);
      final authorizations = await Future.wait(order.authorizations.map(acmeClient.authorize));
      await Future.wait(authorizations.map((auth) async {
        // 在这里你需要处理DNS挑战或其他类型的挑战
        // 这里假设你已经完成了挑战并验证了域名所有权
      }));
      final certificate = await acmeClient.finalizeOrder(order, csr: File('path/to/your/csr.pem').readAsBytesSync());

      // 保存证书到文件
      File('path/to/save/your/certificate.pem').writeAsStringSync(certificate.certificatePem);
      File('path/to/save/your/chain.pem').writeAsStringSync(certificate.chainPem);
      File('path/to/save/your/privateKey.pem').writeAsStringSync(certificate.privateKeyPem);

      setState(() {
        _certificateStatus = 'Certificate fetched successfully!';
      });
    } catch (e) {
      setState(() {
        _certificateStatus = 'Error fetching certificate: ${e.message}';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('ACME Client Demo'),
        ),
        body: Center(
          child: Text(_certificateStatus),
        ),
      ),
    );
  }
}

注意事项

  1. 私钥和注册信息:在实际使用中,你需要一个有效的私钥和一个已经注册好的ACME账户信息。私钥通常通过工具如openssl生成,注册信息则是在首次与ACME服务器交互时获得的。

  2. DNS挑战:上面的代码示例中省略了处理DNS挑战的部分。在实际应用中,你需要根据ACME服务器的挑战要求,更新你的DNS记录以证明你对域名的所有权。这通常涉及自动化脚本或API调用到你的DNS提供商。

  3. 错误处理:在实际应用中,你应该添加更详细的错误处理逻辑,以处理各种可能的异常情况,如网络错误、验证失败等。

  4. 安全性:确保私钥和证书文件的安全存储,避免泄露给未经授权的第三方。

  5. 依赖版本:确保你使用的acme_client插件版本与你的Flutter环境兼容,并查阅插件的官方文档以获取最新的API信息和最佳实践。

回到顶部