#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; };
Убедитесь, что вы указали пароль в обоих случаях.