首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >2021年9月30日之后使用LetsEncrypt SSL证书的安卓7 CERTIFICATE_VERIFY_FAILED上的Flutter

2021年9月30日之后使用LetsEncrypt SSL证书的安卓7 CERTIFICATE_VERIFY_FAILED上的Flutter
EN

Stack Overflow用户
提问于 2021-10-09 22:48:28
回答 4查看 2.8K关注 0票数 18

2021年9月30日之后,在旧的Android 7设备上使用让我们加密SSL证书的https get/post请求失败,并显示以下错误:

代码语言:javascript
复制
HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: certificate has expired(handshake.cc:354))

这个错误不会出现在较新的Android或Apple设备上。

为什么这个错误突然出现在旧的Android手机上?

我该如何解决这个问题?

EN

回答 4

Stack Overflow用户

发布于 2021-10-09 22:48:28

解决方案

在Flutter中,为了再次在旧设备上建立SSL连接,让我们加密受SSL保护的网站,我们可以通过SecurityContextdart:io HttpClient对象(从dart本地通信库)提供Let's Encrypt的可信证书,我们可以直接使用它来进行https get/post调用,或者如果我们正在使用该popular pub.dev package,我们可以将自定义HttpClient提供给Flutter/Dart package:http IOClient

示例

这是一个Flutter单元测试,它创建了一个带有SecurityContextdart:io HttpClient,该SecurityContext提供了一个Let's Encrypt根证书。然后,这个HttpClient被提供给package:http IOClient,它实现了Client接口,可以用于所有常见的getpost等调用。

代码语言:javascript
复制
import 'dart:convert';
import 'dart:typed_data';
import 'dart:io';

import 'package:test/test.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';

void main() {
  const sslUrl = 'https://valid-isrgrootx1.letsencrypt.org/';

  /// From dart:io, create a HttpClient with a trusted certificate [cert]
  /// added to SecurityContext.
  /// Wrapped in try catch in case the certificate is already trusted by
  /// device/os, which will cause an exception to be thrown.
  HttpClient customHttpClient({String cert}) {
    SecurityContext context = SecurityContext.defaultContext;

    try {
      if (cert != null) {
        Uint8List bytes = utf8.encode(cert);
        context.setTrustedCertificatesBytes(bytes);
        print('createHttpClient() - cert added!');
      }
    } on TlsException catch (e) {
      if (e?.osError?.message != null &&
          e.osError.message.contains('CERT_ALREADY_IN_HASH_TABLE')) {
        print('createHttpClient() - cert already trusted! Skipping.');
      } else {
        print('createHttpClient().setTrustedCertificateBytes EXCEPTION: $e');
        rethrow;
      }
    }

    return new HttpClient(context: context);
  }

  /// Use package:http Client with our custom dart:io HttpClient with added
  /// LetsEncrypt trusted certificate
  http.Client createLEClient() {
    IOClient ioClient;
    ioClient = IOClient(customHttpClient(cert: ISRG_X1));
    return ioClient;
  }

  /// Example using a custom package:http Client
  /// that will work with devices missing LetsEncrypt
  /// ISRG Root X1 certificates, like old Android 7 devices.
  test('HTTP client to LetsEncrypt SSL website', () async {
    http.Client _client = createLEClient();
    http.Response _response = await _client.get(sslUrl);
    print(_response.body);
    expect(_response.statusCode, 200);
    _client.close(); // remember to close client as per https://pub.dev/packages/http
  });
}

/// This is LetsEncrypt's self-signed trusted root certificate authority
/// certificate, issued under common name: ISRG Root X1 (Internet Security
/// Research Group).  Used in handshakes to negotiate a Transport Layer Security
/// connection between endpoints.  This certificate is missing from older devices
/// that don't get OS updates such as Android 7 and older.  But, we can supply
/// this certificate manually to our HttpClient via SecurityContext so it can be
/// used when connecting to URLs protected by LetsEncrypt SSL certificates.
/// PEM format LE self-signed cert from here: https://letsencrypt.org/certificates/
const String ISRG_X1 = """-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----""";

由于此单元测试是在具有ISRG Root X1证书的上运行的台式机/膝上型计算机上运行的,因此它可能不是很有趣/有用。获得更新的系统将安装此证书颁发机构(CA)证书,并且“应该”没有问题地验证“信任链”让我们加密SSL证书。

但是在没有ISRG根X1证书的旧设备上,使用上面的两个函数customHttpClient()createLEClient(),当LE的CA证书(ISRG根X1)丢失时,我们可以建立https/TLS连接让我们加密受SSL保护的互联网资源。

为什么会发生这种情况?

让我们加密SSL证书是使用来自数字签名信任( Digital Signature Trust,DST)的交叉签名创建/颁发的,数字签名信任(Digital Signature Trust,DST)是一个较旧的、建立良好的证书颁发机构(CA)。

被广泛信任的CA交叉签名意味着让我们的Ecrypt (LE) SSL证书从第一天(大约5年前)开始就被几乎每个应用程序和设备接受为合法。

DST用于交叉签署证书的证书已于9月9日过期。30,2021年。这意味着LE certs的“信任链”不再被一些较老的设备所接受。

有几种解决方案可以解决这个问题,这只是一种不需要最终用户干预的方法。

为什么这会影响Android 7.1.1之前的Flutter

(这是我的猜测……)Dart VM (&因此,Flutter)使用OpenSSL的Google分支BoringSSL library

当找到任何匹配的信任链、无效(即过期)或其他情况时,Dart VM中的BoringSSL将停止搜索有效的信任链。谷歌的Dart团队遇到了这个issue in June (不是因为Let's Encrypt的DST交叉签名过期,而是一个类似的问题),并创建了一个patch for it on Aug 26。该补丁可以与Dart 2.15一起推出。当这个版本的Dart进入Flutter时,我希望/猜这个补丁能解决这个问题。

更多信息

Background on expiration of DST root cert from LE

More background on DST expiration & cert chaining help from LE

Let's Encrypt has an ongoing mega-thread for the issues caused by the DST root cert expiration here

票数 19
EN

Stack Overflow用户

发布于 2021-11-13 17:12:31

要全局信任新的ISRG证书(请参阅@Baker和@Westy92的答案),只需在main()的开头添加以下内容

代码语言:javascript
复制
try {
  SecurityContext.defaultContext.setTrustedCertificatesBytes(ascii.encode(ISRG_X1));
} catch (e) {
  // ignore errors here, maybe it's already trusted
}

这将使证书在所有HttpClient实例中都是可信的,包括NetworkImageCachedNetworkImagegrpc

票数 1
EN

Stack Overflow用户

发布于 2022-02-12 08:38:45

感谢@esty92和@3xecutor,

https://letsencrypt.org/certs/lets-encrypt-r3.pem下载证书

将根证书值放入assets/ca中:

您还可以在assets/ca中使用此名称创建一个文件,并复制此证书init。

文件名“lets encrypt-r3.pem”

代码语言:javascript
复制
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----

然后像这样添加assets/ca路径到您的pubspec.yaml

代码语言:javascript
复制
 flutter:
  uses-material-design: true
  assets:
     .
     .
     .
    - assets/ca/

然后,只需在您的main.dart中添加以下内容:

代码语言:javascript
复制
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
  SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());

  runApp(MyApp());
}

这个方法解决了我的问题,适用于低于7.1.1版本的android。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69511057

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档