При работе с HTTPS-запросами в Delphi с использованием компонента TNetHTTPClient разработчики часто сталкиваются с необходимостью валидации серверных сертификатов. В этой статье мы разберем проблему, описанную пользователем MMSoft, и предложим несколько решений для корректной работы с PEM-сертификатами в Delphi, особенно на платформе Android.
Понимание проблемы
Пользователь пытается использовать PEM-сертификат с TNetHTTPClient в Delphi 12.1 для Android. В текущей реализации он просто отключает проверку сертификата через событие OnValidateServerCertificate, что не является безопасным решением.
Как правильно отметил Kas Ob., такая реализация имеет несколько недостатков:
- Отсутствие проверки сертификата делает соединение уязвимым к MITM-атакам
- Нет гарантии, что клиент соединяется именно с нужным сервером
- При изменении сертификата на сервере клиент продолжит работать, что может быть нежелательно
Решение 1: Валидация сертификата по отпечатку
Наиболее простой и достаточно надежный способ - проверять отпечаток (fingerprint) сертификата:
procedure TForm1.NetHTTPClient1ValidateServerCertificate(const Sender: TObject;
const ARequest: TURLRequest; const Certificate: TCertificate;
var Accepted: Boolean);
const
VALID_CERT_THUMBPRINT = 'A1B2C3D4E5F6...'; // Замените на реальный отпечаток
begin
Accepted := Certificate.Thumbprint = VALID_CERT_THUMBPRINT;
end;
Чтобы получить отпечаток сертификата, можно использовать OpenSSL:
Решение 2: Загрузка PEM-сертификата и полная проверка
Для более полной проверки можно загрузить PEM-сертификат и сравнивать его с полученным от сервера:
uses
System.Classes, System.SysUtils, System.NetEncoding;
procedure TForm1.LoadCertificate;
var
CertStream: TStringStream;
begin
CertStream := TStringStream.Create;
try
// Загружаем PEM-файл из ресурсов или файловой системы
CertStream.LoadFromFile(TPath.Combine(TPath.GetDocumentsPath, 'certificate.pem'));
FCertificateData := CertStream.DataString;
finally
CertStream.Free;
end;
end;
procedure TForm1.NetHTTPClient1ValidateServerCertificate(const Sender: TObject;
const ARequest: TURLRequest; const Certificate: TCertificate;
var Accepted: Boolean);
var
ServerCert: string;
begin
ServerCert := '-----BEGIN CERTIFICATE-----' + sLineBreak +
TNetEncoding.Base64.EncodeBytesToString(Certificate.Certificate) +
sLineBreak + '-----END CERTIFICATE-----';
Accepted := ServerCert = FCertificateData;
end;
Решение 3: Использование хранилища сертификатов
Более правильным подходом будет использование хранилища доверенных сертификатов. На Android можно использовать встроенное хранилище или добавить свой сертификат:
procedure TForm1.AddCustomCertificateToStore;
var
CertFile: string;
X509Cert: JX509Certificate;
CertFactory: JCertificateFactory;
Input: JInputStream;
begin
CertFile := TPath.Combine(TPath.GetDocumentsPath, 'certificate.pem');
if TFile.Exists(CertFile) then
begin
Input := TJFileInputStream.JavaClass.init(StringToJString(CertFile));
try
CertFactory := TJCertificateFactory.JavaClass.getInstance(StringToJString('X.509'));
X509Cert := TJX509Certificate.Wrap(CertFactory.generateCertificate(Input));
// Создаем KeyStore и добавляем сертификат
FKeyStore := TJKeyStore.JavaClass.getInstance(TJKeyStore.JavaClass.getDefaultType);
FKeyStore.load(nil, nil);
FKeyStore.setCertificateEntry('my_cert', X509Cert);
finally
Input.close;
end;
end;
end;
Решение для Android
На платформе Android работа с сертификатами имеет свои особенности. Вот пример настройки TNetHTTPClient для Android:
При работе с сертификатами важно правильно обрабатывать ошибки:
procedure TForm1.NetHTTPClient1RequestError(const Sender: TObject;
const AError: string);
begin
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add('Ошибка: ' + AError);
end);
end;
procedure TForm1.NetHTTPClient1RequestException(const Sender: TObject;
const AError: Exception);
begin
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add('Исключение: ' + AError.Message);
if AError is ENetHTTPCertificateException then
Memo1.Lines.Add('Проблема с сертификатом: ' +
ENetHTTPCertificateException(AError).Certificate.Thumbprint);
end);
end;
Рекомендации по безопасности
Не отключайте проверку сертификата в продакшен-коде
Используйте актуальные версии протоколов (TLS 1.2 или выше)
Обновляйте сертификаты при их изменении на сервере
Для критических приложений реализуйте отзыв сертификатов
Логируйте ошибки валидации для последующего анализа
Заключение
Работа с PEM-сертификатами в Delphi требует понимания принципов TLS-аутентификации. В статье представлено несколько подходов - от простой проверки отпечатка до полной настройки хранилища сертификатов на Android. Выбор метода зависит от требований безопасности и специфики вашего приложения.
Помните, что простое отключение проверки сертификатов (Accepted := True) - это не решение, а создание потенциальной уязвимости. Используйте представленные в статье методы для безопасной работы с HTTPS-соединениями в ваших Delphi-приложениях.
Статья описывает методы работы с PEM-сертификатами в Delphi, включая проверку отпечатка, загрузку сертификата и использование хранилища доверенных сертификатов, а также альтернативные решения с Indy, с акцентом на безопасность и особенности работы на And
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.