#flutter #dart #https #self-signed-certificate
Вопрос:
Мне нужно использовать самозаверяющий сертификат https с приложением flutter. К сожалению, я не могу решить проблему. Что бы я ни пытался, я всегда получаю эту ошибку «[VERBOSE-2:ui_dart_state.cc(199)] Необработанное исключение: Исключение рукопожатия: Ошибка рукопожатия в клиенте (Ошибка ОС: CERTIFICATE_VERIFY_FAILED: ошибка проверки приложения(рукопожатие.cc:359))»
Я попробовал самоподписанный сертификат, попробовал с самоподписью с CA — безуспешно. Попытался понизить версию сервера до tls1.2 (я нашел некоторые старые проблемы с этим) — не помогает. Это и есть код:
ByteData bytes = await rootBundle.load('ssl/certificate2019.pem');
SecurityContext clientContext = SecurityContext();
clientContext.setTrustedCertificatesBytes((bytes.buffer.asInt8List()));
var httpClient = new HttpClient(context: clientContext);
httpClient.badCertificateCallback = (X509Certificate cert, String host, int port){
print("!!!!Bad certificate host $host $port");
return false;
};
var uri = Uri.parse('https://localhost:443');
final result = await httpClient.getUrl(uri).timeout(Duration(milliseconds: 3000));
result.close();
Похоже, что setTrustedCertificatesBytes в целом не имеет никакого значения с пустым контекстом, ошибка та же ошибка проверки приложения(рукопожатие.cc:359)
Последнее, что я пробовал, — это примеры отсюда https://github.com/dart-lang/sdk/issues/35981#issuecomment-536134040 от @M.Леонард, я полагаю, которые исключают мобильный(flutter),потому что это dart и исключают мой сервер (сервер внутри примера сценария), но результат тот же. Это результат сценария dart из примера по ссылке выше
Пример исходного кода:
import 'dart:io' show BytesBuilder, File, HttpClient, HttpClientRequest, HttpClientResponse, HttpHeaders, HttpRequest, HttpServer, InternetAddress, Process, SecurityContext, stderr, stdout;
import 'dart:convert' show utf8;
Future<void> shellCommand(String command) async {
print('Executing command $command');
final Process process = await Process.start('sh', ['-c', command]);
stdout.addStream(process.stdout);
stderr.addStream(process.stderr);
final int exitCode = await process.exitCode;
if (exitCode != 0) {
throw new Exception('Process exited with status $exitCode');
}
}
void main() async {
// Last year's certificates:
await shellCommand('openssl req -newkey rsa:2048 -nodes -keyout ca2018.privatekey.pem -subj "/OU=CA" -days 731 -x509 -out ca2018.certificate.pem');
// This year's certificates:
await shellCommand('openssl req -newkey rsa:2048 -nodes -keyout ca2019.privatekey.pem -subj "/OU=CA" -days 731 -x509 -out ca2019.certificate.pem');
await shellCommand('openssl req -newkey rsa:2048 -passout pass:password -keyout privatekey.pem -subj "/CN=localhost" -days 731 -sha256 -new -out csr2019.pem');
await shellCommand('openssl x509 -req -in csr2019.pem -CA ca2019.certificate.pem -CAkey ca2019.privatekey.pem -set_serial 1 -days 730 -sha256 -out certificate2019.pem');
await shellCommand('cat certificate2019.pem ca2019.certificate.pem > certificate2019.chain.pem');
final SecurityContext serverSecurityContext = new SecurityContext();
serverSecurityContext.useCertificateChainBytes(await new File('certificate2019.chain.pem').readAsBytes());
serverSecurityContext.usePrivateKey('privatekey.pem', password: 'password');
final HttpServer httpServer = await HttpServer.bindSecure(InternetAddress.loopbackIPv4, 0, serverSecurityContext);
httpServer.listen((HttpRequest request) {
request.response.write('body1');
request.response.close();
});
print('Server listening at https://localhost:${httpServer.port}/');
print('Making request.');
final SecurityContext clientSecurityContext = new SecurityContext(withTrustedRoots: false);
clientSecurityContext.setTrustedCertificatesBytes(await new File('ca2018.certificate.pem').readAsBytes());
clientSecurityContext.setTrustedCertificatesBytes(await new File('ca2019.certificate.pem').readAsBytes());
final HttpClient httpClient = new HttpClient(context: clientSecurityContext);
final HttpClientRequest request = await httpClient.getUrl(Uri(scheme: 'https', host: 'localhost', port: httpServer.port, path: '/'));
final HttpClientResponse response = await request.close();
final List<int> bytes = await response.fold(new BytesBuilder(), (BytesBuilder bytesBuilder, List<int> bytes) {
bytesBuilder.add(bytes);
return bytesBuilder;
}).then((BytesBuilder bytesBuilder) => bytesBuilder.takeBytes());
final String contenType = response.headers.value(HttpHeaders.contentTypeHeader) ?? '';
print('${response.statusCode} ${response.reasonPhrase} '
'content-type="$contenType" body="${utf8.decode(bytes)}"');
httpServer.close(force: true);
}
I can successfully do requests over https with postman with added .pem file to postman.
Also I can successfully do a request with curl
I cant figure out what I made wrong… Why setTrustedCertificatesBytes does not affect?
I also tried with facebook certificate (download it and convert into pem) and SecurityContext() which by default exclude all systems certificates, (withTrustedRoots is false by default) and it works well. It seems that it checks certificate inside, and can verify, but cant verify self signed certificates! But I cant understand why this examples (like above) works? I suppose they should work if it is publushed .. so I do somthing wrong but can’t understand what exactly..
Will wait any help! Thanks for readind till the end)
UPDATE ——
I tried do all steps again.
openssl commands I used
- Root Private Key
openssl genrsa -out rootCA.key 2048
- Root CA certificate
openssl req -x509 -new -key rootCA.key -sha256 -days 365 -out rootCA.pem
- Server Private key
openssl genrsa -out server.key 2048
- Sign request
openssl req -new -key server.key -out server.csr
- Sign server Cert with CA
openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -days 365 -sha256 -out server.pem
- Concatenate into chain
cat server.pem rootCA.pem > chain.pem
I also entered company, in some places it is mentioned as important.
Dart server code (in same directory with certificates)
void main() async {
final SecurityContext serverSecurityContext = new SecurityContext();
serverSecurityContext.useCertificateChainBytes(await new File('chain.pem').readAsBytes());
serverSecurityContext.usePrivateKey('server.key');
final HttpServer httpServer = await HttpServer.bindSecure(InternetAddress.loopbackIPv4, 0, serverSecurityContext);
httpServer.listen((HttpRequest request) {
request.response.write('body1');
request.response.close();
});
print('Server listening at https://localhost:${httpServer.port}/');
}
Request script code (in the same directory)
void main() async {
print('Making request.');
final SecurityContext clientSecurityContext = new SecurityContext();
clientSecurityContext.setTrustedCertificatesBytes(await new File('rootCA.pem').readAsBytes());
final HttpClient httpClient = new HttpClient(context: clientSecurityContext);
final HttpClientRequest request = await httpClient.getUrl(Uri(scheme: 'https', host: 'localhost', port: 443, path: '/'));
final HttpClientResponse response = await request.close();
final List<int> bytes = await response.fold(new BytesBuilder(), (BytesBuilder bytesBuilder, List<int> bytes) {
bytesBuilder.add(bytes);
return bytesBuilder;
}).then((BytesBuilder bytesBuilder) => bytesBuilder.takeBytes());
final String contenType = response.headers.value(HttpHeaders.contentTypeHeader) ?? '';
print('${response.statusCode} ${response.reasonPhrase} '
'content-type="$contenType" body="${utf8.decode(bytes)}"');
}
Результат openssl s_client -подключение локального хоста:55132 -CAfile RootCA.pem
В результате запроса от скрипта Dart я получил ту же ошибку( я также попробовал другой сервер, не Dart — та же ошибка на клиенте. Я все еще могу связаться с почтальоном с помощью RootCA.pem.
Может быть, я что-то не так делаю с сертификатами, которые генерирую.. И я не могу найти эту конкретную ошибку в Интернете, я проверяю время, когда она правильна.. Может быть, есть какие-нибудь предложения, что еще я могу сделать?
Комментарии:
1. Когда вы звоните
setTrustedCertificateBytes
, вам нужно передать сертификат сертификата подписи (т. е. сертификат центра сертификации). Судя по скриншоту, который выглядит такca2019.certificate.pem
. Вы не должны передавать сертификат сервера. Проверьте выводopenssl s_client
тоже.2. Спасибо @Richard! Не могли бы вы сообщить более подробную информацию, пожалуйста?
3. Измените это:
ByteData bytes = await rootBundle.load('ssl/ca2019.certificate.pem');
И, конечно, включите это в активы.4. Спасибо за ваш ответ @Richard! Кажется, я написал что-то непонятное. Я пытался, но безуспешно, может быть, мисс что-нибудь попытается снова.. Но одна вещь меня смутила, пример выше, как я понимаю, использует ca2019.certificate.pem для setTrustedCertificateBytes. Я запустил пример без каких-либо изменений, но получил ту же ошибку.. Первый снимок экрана является результатом examle, но третий пример, не первый и не второй, и, как я понимаю, examole использует сертификаты CA для setTrustedCertificateBytes.. но не работай на меня. Поэтому мне нужно попробовать сертификат сервера, подписанный сертификатом самостоятельного центра сертификации, верно?
5. @RichardHeap Я отредактировал вопрос и добавил исходный код примера, который я использовал