Bak*_*ker 26 android ssl-certificate flutter lets-encrypt
2021 年 9 月 30 日之后,在旧版 Android 7 设备上使用 Let's Encrypt SSL 证书向网站发出 https get/post 请求失败,并出现以下错误:
HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: certificate has expired(handshake.cc:354))
Run Code Online (Sandbox Code Playgroud)
此错误不会发生在较新的 Android 或 Apple 设备上。
为什么旧的 Android 手机会突然出现此错误?
我该如何解决这个问题?
Bak*_*ker 30
在 Flutter 中,为了再次在旧设备上与 Let's Encrypt SSL 保护的网站建立 SSL https 连接,我们可以通过对象(来自 dart 本机通信库)提供 Let's Encrypt 的可信证书SecurityContext,dart:io HttpClient我们可以直接使用该证书来进行 https get/post调用,或者如果我们使用流行的 pub.dev 包,HttpClient我们可以提供针对 Flutter/Dart的定制。package:http IOClient
这是一个 Flutter 单元测试,它创建了一个dart:io HttpClient带有SecurityContext提供给它的 Let's Encrypt 根证书的 a 。然后,它HttpClient被提供给package:http IOClient哪个实现的Client接口,并且可以用于所有通常的get调用post等。
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-----""";
Run Code Online (Sandbox Code Playgroud)
由于此单元测试是在具有ISRG Root X1 证书的台式机/笔记本电脑上运行的,因此它可能不是很有趣/有用。获得更新的系统将安装此证书颁发机构 (CA) 证书,并且“应该”在验证 Let's Encrypt SSL 证书的“信任链”时没有问题。
但在没有 ISRG 根 X1 证书且永远不会有的旧设备上,使用上述两个函数,当LE的CA 证书(ISRG 根 X1)丢失时,我们可以与 Let's Encrypt SSL 保护的互联网资源建立 https/TLS 连接。customHttpClient()createLEClient()
Let's Encrypt SSL 证书是通过数字签名信任 (DST)(一个较早且完善的证书颁发机构 (CA))的交叉签名创建/颁发的。
由广泛信任的 CA 进行交叉签名意味着 Let's Ecrypt 的 (LE) SSL 证书从第一天(大约 5 年前)起就被几乎所有应用程序和设备视为合法。
DST 用于交叉签署 LE 证书的证书已于 2021 年 9 月 30 日到期。这意味着 LE 证书的“信任链”不再被某些旧设备接受。
有多种解决方案可以解决此问题,这只是一种不需要最终用户干预的方法。
(这是我的猜测...)Dart VM(因此,Flutter)使用BoringSSL 库,这是 OpenSSL 的 Google 分支。
当找到任何匹配的信任链、无效(即过期)或其他情况时,Dart VM 中的 BoringSSL 将停止搜索有效的信任链。Google 的 Dart 团队在 6 月份遇到了这个问题(不是因为 Let's Encrypt 的 DST 交叉签名过期,而是因为类似的问题),并于 8 月 26 日为其创建了补丁。该补丁可能会随 Dart 2.15 一起推出。当该版本的 Dart 被引入 Flutter 时,我希望/猜测这个补丁能够解决这个问题。
Let's Encrypt 有一个持续的大型线程,用于解决 DST 根证书过期引起的问题
tah*_*amv 27
谢谢@esty92和@3xecutor,
从https://letsencrypt.org/certs/lets-encrypt-r3.pem下载证书
将根证书值放入 asset/ca 中:
您还可以在 asset/ca 中创建一个具有此名称的文件,并复制此证书 init。
文件名“lets-encrypt-r3.pem”
-----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-----
Run Code Online (Sandbox Code Playgroud)
然后将 asset/ca 路径添加到您的 pubspec.yaml 中,如下所示
flutter:
uses-material-design: true
assets:
.
.
.
- assets/ca/
Run Code Online (Sandbox Code Playgroud)
然后只需将其添加到您的 main.dart 中:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
runApp(MyApp());
}
Run Code Online (Sandbox Code Playgroud)
这个方法解决了我的问题,它适用于版本低于7.1.1的android。