Решаем проблему "SSLHandshakeException: Received fatal alert: certificate_unknown" в BrowserMob-Proxy

Библиотека BrowserMob-Proxy часто используется в паре с Selenium для захвата или модификации трафика между браузером, работающим под управлением автоматизированных тестов и тестируемым веб-приложением. Однако, как это часто бывает, когда вы имеете дело с протоколом HTTPs, не всегда дела идут гладко.

В данной статье мы взглянем на одну из подобный проблем. Ошибку, возникающую при обращении к HTTPs-адресу:

ERROR l.ClientToProxyConnection| (NEGOTIATING_CONNECT) [id: 0x38d98718, L:0.0.0.0/0.0.0.0:34075 ! R:/127.0.0.1:37270]: Caught an exception on ClientToProxyConnection
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:461)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:267)

Давайте посмотрим почему она может возникнуть и как эту проблему решить.

Почему такое происходит

В двух словах, это происходит потому, что браузер не доверяет сертификату, получаемому от сервера. Однако, когда мы говорим о случае с прокси, ваш клиент получает сертификат не от самого тестируемого сайта, а от сервера BrowserMob-Proxy.

Эксперимент

Давайте запустим наш прокси в standalone-режиме, либо просто поставим наш код на паузу сразу после того как прокси-сервер стартовал. Затем мы сконфигурируем наш браузер таким образом, чтобы тот использовал запущенный прокси, пропуская вызовы через него. Теперь вручную откроем страничку "google.com"

Что мы видим? Мы видим, что браузер сообщает нам, что соединение небезопасно. Взглянем на детали сертификата:

browsermob proxy ssl certificate error

Детали сертификата говорят о том, что сертификат действительно выдан для доменного имени "google.com". Но есть ещё одна деталь: издателем этого сертификата является некая организация "LittleProxy MITM".

Итак, причиной, по которой сертификат, выданный для "google.com" не проходит валидацию браузером, является то, что сертификат выпущен издателем, которому браузер не доверяет.

Еще немного деталей

Когда вы обращаетесь к сайту (например по адресу "google.com") без использования BrowserMob-Proxy, сервер гугла отправляет вам цепочку SSL-сертификатов. С одной стороны этой цепочки находится сертификат, выданный непосредственно для вызываемого домена. С другой стороны (в корне) этой цепочки находится "корневой" сертификат (для гугла - это сертификат сертификационного агентства "GlobalSign"). Корневые сертификаты большинства известных сертификационных агентств по умолчанию устанавливаются в доверенные хранилища операционных систем либо браузеров.

Когда вы используете BrowserMob-Proxy, сервер прокси становится т.н. MITM (Man In The Middle). Он подменяет оригинальный сертификат сайта на собственноручно выпущенный (генерируемый "на лету"). Схема описанного взаимодействия может выглядеть так:

browsermob proxy mitm

Так как ни ваш браузер ни ваша операционная система не имеют в своих доверенных хранилищах сгенерированный на лету сертификат, браузер считает такое соединение небезопасным.

Решение

Позади довольно долгое вступление, тем не менее всегда лучше понимать причину, лежащую в корне проблемы, чем просто знать как эта проблема решается.

Существует два пути решения. Первый способ - сохранить сертификат, сгенерированный прокси-сервером, и далее переиспользовать его во всех последующих запусках. Негативной стороной такого решения выступает необходимость кодирования дополнительной логики в ваших тестах. Вы можете найти примеры такой логики (а также ряд примеров для других конфигураций) на страничке BrowserMob-Proxy в github.

Простой способ

Гораздо более простым способом является выдача используемому в тесте браузеру разрешения доверять вообще всем сертификатам. Реализация такого решения всегда начинается с запуска прокси-сервера:

browserMobProxy = new BrowserMobProxyServer();
browserMobProxy.start(0);
Proxy proxy = ClientUtil.createSeleniumProxy(browserMobProxy);

И заканчивается запуском браузера со специальной настройкой. То, как эта настройка передается, зависит от используемого браузера. Ниже вы можете найти примеры для браузеров, поддержка которых включена в стандартную поставку Selenium:

Chrome:

ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setProxy(proxy);
chromeOptions.setAcceptInsecureCerts(true);
driver = new ChromeDriver(chromeOptions);

Firefox:

FirefoxOptions firefoxOptions = new FirefoxOptions();
firefoxOptions.setAcceptInsecureCerts(true);
firefoxOptions.setProxy(proxy);
driver = new FirefoxDriver(firefoxOptions);

Opera:

OperaOptions operaOptions = new OperaOptions();
operaOptions.setProxy(proxy);
operaOptions.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);
driver = new OperaDriver();

Safari:

SafariOptions safariOptions = new SafariOptions();
safariOptions.setProxy(proxy);
safariOptions.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);
driver = new SafariDriver();

Internet Explorer:

InternetExplorerOptions internetExplorerOptions = new InternetExplorerOptions();
internetExplorerOptions.setProxy(proxy);
internetExplorerOptions.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);
driver = new InternetExplorerDriver();

Это, в общем-то, всё. В заключение, хочу добавить, что очень ценю обратную связь, получаемую от вас. Прошу оставлять отзывы, пожелания и предложения, используя эту форму обратной связи. Опираясь на ваши пожелания, я формирую будущие темы, а также исправляю существующие статьи.