Исключение Flutter TlsException: Отказ доверять встроенным корням

#flutter #dart #ssl #ssl-certificate #sslpinning

Вопрос:

Я пытаюсь выполнить закрепление SSL-сертификата в приложении Flutter с помощью HttpClient . Ранее я успешно выполнял закрепление в родном приложении для Android. Это сообщение об ошибке, которое я получаю:

 E/flutter (28810): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Exception: TlsException: Failure trusting builtin roots (OS Error:  E/flutter (28810): BAD_PKCS12_DATA(pkcs8_x509.c:645), errno = 0) E/flutter (28810): #0 _SecurityContext.setTrustedCertificatesBytes (dart:io-patch/secure_socket_patch.dart:233:59) E/flutter (28810): #1 ApiProvider._clientWithPinnedCertificate (package:app/api/api.provider.dart:68:13) E/flutter (28810): #2 ApiProvider.makePostRequest (package:app/api/api.provider.dart:45:11) E/flutter (28810): #3 _fireAuthenticationRequestIsolate (package:app/api/repositories/auth.repository.dart:14:41) E/flutter (28810): #4 _IsolateConfiguration.apply (package:flutter/src/foundation/_isolates_io.dart:84:34) E/flutter (28810): #5 _spawn.lt;anonymous closuregt; (package:flutter/src/foundation/_isolates_io.dart:91:65) E/flutter (28810): #6 _spawn.lt;anonymous closuregt; (package:flutter/src/foundation/_isolates_io.dart:90:5) E/flutter (28810): #7 Timeline.timeSync (dart:developer/timeline.dart:163:22) E/flutter (28810): #8 _spawn (package:flutter/src/foundation/_isolates_io.dart:88:35) E/flutter (28810): #9 _delayEntrypointInvocation.lt;anonymous closuregt; (dart:isolate-patch/isolate_patch.dart:286:17) E/flutter (28810): #10 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)  

После загрузки .cer файла я преобразовал его в .pem с помощью следующей команды:

 openssl x509 -inform pem -in test.ca -outform der -out test.pem  

Я загружаю сертификат из assets папки следующим образом:

 const certificatePath = 'assets/test.pem'; final certificateBytes = await rootBundle.load(certificatePath); final certificate = certificateBytes.buffer.asUint8List();  

Это код поставщика API, в котором выполняется запрос HTTP POST

 class ApiProvider {  final String _baseURL;   ApiProvider(String baseURL) : _baseURL = baseURL;   Futurelt;Stringgt; makePostRequest(  String endpoint,  Maplt;String, dynamicgt; body, {  Maplt;String, Stringgt;? headers,  bool setTrustedCertificate = false,  }) async {  final client = setTrustedCertificate  ? _clientWithPinnedCertificate()  : HttpClient();   final url = Uri.https(_baseURL, endpoint);  final response = await client.postUrl(url)  ..headers.addAll(headers ?? lt;String, Stringgt;{})  ..write(json.encode(body));  final request = await response.close();   if (request.statusCode lt; 200 || request.statusCode gt;= 300) {  throw ApiException(  request.statusCode,  request.reasonPhrase,  );  }  final responseBody = await request  .transform(const Utf8Decoder(allowMalformed: true))  .reduce((previous, element) =gt; previous   element);  return responseBody;  }   HttpClient _clientWithPinnedCertificate() {  final context = SecurityContext();  context.setTrustedCertificatesBytes(  GlobalConstants.certificate,  password: certificatePassword,  );  final client = HttpClient(context: context);   client.badCertificateCallback = (  X509Certificate certificate,  String host,  int port,  ) {  print('Bad certificate: ${certificate.sha1} for host $host:$port');  return false;  };   return client;  } }  

Ответ №1:

После долгого времени игры с различными конфигурациями мне удалось найти решение, и оно немного глубже, чем я думал.

Во-первых, убедитесь, что вы не звоните rootBundle.load() в отдельный изолятор. Я делал это, и это приводило к неоднозначным ошибкам.

Во-вторых, если у вас есть .p12 файл, вам не нужен .ca (или преобразование в .pem ). Все, что вам нужно сделать, это использовать следующий фрагмент кода

 // Here, certificate is a Uint8List final context = SecurityContext.defaultContext  ..useCertificateChainBytes(  certificate,  password: /* input certificate passphrase */  ),  ..usePrivateKeyBytes(  certificate,  password: /* input certificate passphrase */  );  final client = HttpClient(context: context); client.badCertificateCallback = (  X509Certificate cert,  String host,  int port, ) {  // Handle certificate that can't be authenticated  // Returning 'true' by itself is not really safe...  return true; };  

Убедитесь, что вы указали пароль в обоих случаях.