2021年9月30日之后,在旧的Android 7设备上使用让我们加密SSL证书的https get/post请求失败,并显示以下错误:
HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: certificate has expired(handshake.cc:354))这个错误不会出现在较新的Android或Apple设备上。
为什么这个错误突然出现在旧的Android手机上?
我该如何解决这个问题?
发布于 2021-10-09 22:48:28
解决方案
在Flutter中,为了再次在旧设备上建立SSL连接,让我们加密受SSL保护的网站,我们可以通过SecurityContext向dart:io HttpClient对象(从dart本地通信库)提供Let's Encrypt的可信证书,我们可以直接使用它来进行https get/post调用,或者如果我们正在使用该popular pub.dev package,我们可以将自定义HttpClient提供给Flutter/Dart package:http IOClient。
示例
这是一个Flutter单元测试,它创建了一个带有SecurityContext的dart:io HttpClient,该SecurityContext提供了一个Let's Encrypt根证书。然后,这个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-----""";由于此单元测试是在具有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
发布于 2021-11-13 17:12:31
要全局信任新的ISRG证书(请参阅@Baker和@Westy92的答案),只需在main()的开头添加以下内容
try {
SecurityContext.defaultContext.setTrustedCertificatesBytes(ascii.encode(ISRG_X1));
} catch (e) {
// ignore errors here, maybe it's already trusted
}这将使证书在所有HttpClient实例中都是可信的,包括NetworkImage、CachedNetworkImage和grpc。
发布于 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”
-----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
flutter:
uses-material-design: true
assets:
.
.
.
- assets/ca/然后,只需在您的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());
}这个方法解决了我的问题,适用于低于7.1.1版本的android。
https://stackoverflow.com/questions/69511057
复制相似问题