Если вам уже доводилось писать приложения на Node/Express, то вы не понаслышке знаете, что такие приложения обычно устанавливаются с HTTPS и сертификатом сервера. Однако в рабочей среде Node.js HTTPS, как правило, не требуется, поскольку Node-приложения прикрыты обратным прокси-сервером (например, Nginx), который и обслуживает сертификаты.

Скорее всего, ваше Node-приложение будет работать как клиент, и ему придется вызывать серверные службы, защищенные HTTPS. По умолчанию Node.js создается с набором часто используемых корневых сертификатов центров сертификации (СА). Но и здесь мы не застрахованы от ошибок в случаях, если Node-приложение осуществляет HTTPS API-вызов серверных служб с самоподписанными сертификатами (например, частных корпоративных СА). Примеры ошибок:

UNABLE_TO_GET_ISSUER_CERT_LOCALLY 
UNABLE_TO_VERIFY_LEAF_SIGNATURE 
DEPTH_ZERO_SELF_SIGNED_CERT

Для устранения этих ошибок потребуется найти их источник.

SSL-рукопожатие

Подпись: (1) “Client Hello”. Криптографическая информация. (2) “Server Hello”. Комбинация шифров — Cipher Suite. Запрос сертификата клиента (не обязательно). (3) Проверка сертификата сервера. Проверка криптографических параметров. (4) Обмен ключами клиента. Отправка информации о секретном ключе (зашифрована с открытым ключом сервера). (5) Отправка сертификата клиента. (6) Проверка сертификата клиента (если требуется). (7) Клиент — “finished”. (8) Сервер — “finished”.

Такие ошибки возникают в процессе SSL-рукопожатия. Клиент пытается установить соединение с сервером через TLS-рукопожатие. TLS-рукопожатие — это обмен сообщениями между клиентом и сервером, в ходе которого они договариваются о версии TLS и комбинации шифров, подтверждают подлинность сервера, а также генерируют ключи сессии.

В шаге 2 сервер отправляет сообщение со своим SSL-сертификатом, а клиент проверяет его через СА, выпустившего данный сертификат. Так подтверждается подлинность сервера, и клиент взаимодействует с истинным владельцем домена.

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

rejectUnauthorized

Простейший способ по устранению подобных ошибок заключается в использовании rejectUnauthorized.

https.request({ 
      ....,
      rejectUnauthorized: false,
    },
...)

Вы также можете задать ее в качестве переменной среды:

NODE_TLS_REJECT_UNAUTHORIZED=0

К сожалению, данный метод не безопасен, поскольку отключает проверку сертификата сервера, из-за чего Node-приложение становится уязвимым для так называемой “атаки посредника”. Получается, что данный способ допустим только в среде разработки, а не в рабочей среде.

Свойство ca

Более безопасным способом будет указать, какой именно CA-сертификат ожидается от сервера. Иначе говоря, название сертификата должно совпадать с сертификатом сервера.

request({ 
   ca: [fs.readFileSync([certificate path])],
   rejectUnauthorized: true,
}

Как вы могли заметить, ca — это массив, в котором при желании можно прописать несколько файлов сертификатов. Минус данного решения в том, что сертификат прописывается в самом в коде. Это весьма чревато в случае, когда нам нужно управлять несколькими версиями сертификатов в разных средах.

NODE_EXTRA_CA_CERTS

В Node версии 7.3.0 и выше появилась переменная среды NODE_EXTRA_CA_CERTS, которая передается в файл СА-сертификата. Это позволяет дополнять «корневые» CA другими сертификатами. Сам файл сертификата должен состоять как минимум из одного доверенного сертификата в PEM-формате.

Обратите внимание, что дополнительные сертификаты не имеют никакой силы, если в HTTPS-клиенте или сервере явно прописано свойство ca.

Как все исправить?

Чтобы избавиться от ошибок сертификатов, следует пользоваться свойством ca или NODE_EXTRA_CA_CERTS. Но появление тех же сообщений об ошибках все еще возможно. Как правило, это вызвано использованием неправильного сертификата. Для каждого из представленных вариантов вы должны получать полную цепочку сертификатов либо иметь как минимум один корневой сертификат CA.

Получить полную цепочку сертификатов можно через OpenSSL:

openssl s_client -connect ${REMHOST}:${REMPORT}

Пример цепочки сертификатов:

Цепочка сертификатов Google CA

Внимание: команда showcerts не всегда работает при выполнении ее с прокси-сервера либо в случаях, когда удаленный сервер использует SNI.

Читайте также:


Перевод статьи Sunny Sun: How to Resolve Certificate Errors in a Node.js App with SSL Calls

Предыдущая статьяПолучаем данные Open Street Map в Python
Следующая статьяКак построить модель машинного обучения, если под рукой нет доступных данных