Устранение ошибок OpenSSL: Решение проблемы unable to get local issuer certificate
При установлении защищённого соединения через OpenSSL (например, при сетевых запросах в среде PHP, использовании команд openssl
или curl
, либо создании SSL/TLS-соединения в приложениях на Node.js или Python), разработчики могут столкнуться с ошибкой 20:unable to get local issuer certificate
. Это распространённая проблема, связанная с валидацией сертификата OpenSSL и особенностями проверки корневых центров сертификации.
С точки зрения безопасности OpenSSL по умолчанию требует доверенный список корневых центров сертификации (CA) для верификации цепочки сертификатов. Если такой доверенный источник не найден или не задан, OpenSSL не может удостовериться в легитимности сертификата сервера и выводит эту ошибку.
В этой статье подробно рассмотрены причины возникновения данной ошибки и приведены решения для среды разработки ServBay. Вы узнаете, как правильно настроить хранилище доверенных CA для PHP, Python, Node.js и команды curl
.
Ошибка 20:unable to get local issuer certificate
Описание проблемы
Когда OpenSSL пытается проверить SSL/TLS-сертификат удалённого сервера, он строит цепочку от сертификата сервера до доверенного корневого CA. Если в локальном хранилище отсутствует промежуточный или корневой сертификат, либо не указан путь к хранилищу доверия (CAFile или CAPath), проверка проваливается с ошибкой 20:unable to get local issuer certificate
.
Иными словами, OpenSSL не может определить, какому центру сертификации доверять, и не подтверждает подлинность сервера.
Версия OpenSSL и пути CA-сертификатов в ServBay
ServBay — современная локальная среда для веб-разработки — инсталлируется с пакетом OpenSSL и набором популярных корневых CA-сертификатов. Версия используемого OpenSSL зависит от типа процессора вашего устройства:
- Для Apple Silicon (чипы серии M): OpenSSL версии 3.2.1
- Для Intel-чипов: OpenSSL версии 1.1.1u
Сами файлы CA (cacert.pem
) и директории сертификатов (certs
) располагаются в директории установки пакета ServBay. Выберите путь в зависимости от вашей платформы:
ini
# Файл со всеми доверенными корневыми сертификатами
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Каталог отдельных файлов сертификатов (чаще всего достаточно cacert.pem, но некоторым приложениям может понадобиться capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# Файл со всеми доверенными корневыми сертификатами
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Каталог отдельных файлов сертификатов
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
ini
# Файл со всеми доверенными корневыми сертификатами
cafile=C:\ServBay\package\common\openssl\3.3\cacert.pem
# Каталог отдельных файлов сертификатов
capath=C:\ServBay\package\common\openssl\3.3\certs
1
2
3
4
2
3
4
Суть решения ошибки unable to get local issuer certificate
в явном указании параметров cafile
или capath
везде, где используется OpenSSL, чтобы система могла найти доверенные корневые сертификаты.
Примеры решения
Ниже показано, как задать хранилище CA для разных инструментов и языков программирования.
Пример 1: Тестирование соединения через команду openssl
Если при проверке соединения с помощью openssl s_client
появляется ошибка:
bash
openssl s_client -quiet -connect gmail.com:443
1
Вы увидите примерно такой вывод с ошибкой verify error:num=20:unable to get local issuer certificate
:
bash
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=1 C=US, O=Google Trust Services, CN=WR2
verify return:1
depth=0 CN=gmail.com
verify return:1
# ... дополнительная информация о соединении ...
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Решение:
Добавьте ключ -CAfile
с указанием файла CA от пакета ServBay:
bash
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/3.2/cacert.pem
1
bash
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
1
После успешной проверки вместо ошибки verify error:num=20
вы увидите только строки verify return:1
:
bash
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify return:1
depth=1 C=US, O=Google Trust Services, CN=WR2
verify return:1
depth=0 CN=gmail.com
verify return:1
# ... дополнительная информация о соединении ...
1
2
3
4
5
6
7
2
3
4
5
6
7
Пример 2: Настройка OpenSSL в PHP
Многие сетевые функции PHP (например, file_get_contents
для HTTPS, stream_socket_client
для SSL, расширение cURL) используют OpenSSL. Корректную работу сертификатов можно обеспечить через настройки в php.ini
либо параметры потока.
Вариант A: Изменение php.ini
(рекомендуется)
Простой глобальный способ — отредактировать нужный файл php.ini
. В панели управления ServBay найдите раздел редактирования php.ini
, перейдите к секции [openssl]
и добавьте (обновите) следующие строки — используйте путь в зависимости от процессора:
ini
[openssl]
; Указать файл доверенных CA-сертификатов
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Указать каталог с файлами сертификатов (необязательно, но рекомендуется)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[openssl]
; Указать файл доверенных CA-сертификатов
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Указать каталог с файлами сертификатов (необязательно, но рекомендуется)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
После изменений перезапустите сервис PHP через ServBay (или сам ServBay), чтобы настройки вступили в силу.
Вариант B: Указание параметра в коде (только для текущего соединения)
Если глобальных изменений не нужно, используйте опции контекста через stream_context_create
:
php
<?php
// Пример: соединение с SMTP-сервером по SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Выберите путь к CA в зависимости от версии ServBay
// Для Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Для Intel:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // Проверка сертификата сервера
'verify_peer_name' => true, // Проверка соответствия имени хоста
'allow_self_signed' => false, // Запретить самоподписанные сертификаты
'cafile' => $caCertFile, // Указать файл CA
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Необязательно, каталог CA
],
];
$context = stream_context_create($contextOptions);
// Создать SSL/TLS-соединение с заданным контекстом
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // таймаут
STREAM_CLIENT_CONNECT,
$context // параметры SSL
);
if ($connection) {
echo "Connection established to $server:$port\n";
// Пример отправки EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // Пример с брендингом ServBay
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Failed to connect to $server:$port. Error: $errstr ($errno)\n";
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Пример 3: Использование OpenSSL в Python (модуль ssl)
Модуль Python ssl
позволяет явно создать контекст SSL/TLS и задать путь к CA:
python
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Выберите путь к CA по версии ServBay
# Для Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# Для Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# Создать стандартный SSL-контекст с CA
context = ssl.create_default_context(cafile=ca_cert_file)
# Можно задать каталог: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Обычное выделение сокета
with socket.create_connection((server, port)) as sock:
# Обернуть сокет в SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL connection established. Negotiated Protocol: {ssock.version()}")
# Пример EHLO
ssock.sendall(b"EHLO servbay.demo\r\n") # Брендинг ServBay
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Failed to connect or SSL error: {e}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Пример 4: Node.js и OpenSSL (модуль tls)
Модуль tls
в Node.js поддерживает указание доверенных CA через свойство ca
в параметрах соединения. Просто прочитайте файл CA из ServBay:
javascript
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // пример — надёжный сайт
const port = 443;
// Выберите путь к CA по версии ServBay
// Для Apple Silicon:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Для Intel:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// Прочитать содержимое файла CA
ca: fs.readFileSync(caCertFile),
// tls по умолчанию проверяет имя хоста (checkServerIdentity).
// При верном CA и действующем сертификате верификация пройдёт успешно.
// Не отключайте checkServerIdentity без явной необходимости — это небезопасно!
// checkServerIdentity: () => { return null; } // <-- крайне не рекомендуется!
};
const socket = tls.connect(options, () => {
console.log('SSL connection established');
// Для HTTPS запросов обычно требуется отправить HTTP-запрос; данный пример демонстрирует установку соединения.
// socket.write('GET / HTTP/1.1\r\nHost: ' + server + '\r\n\r\n');
});
socket.on('data', (data) => {
console.log(data.toString());
});
socket.on('close', () => {
console.log('Connection closed');
});
socket.on('error', (error) => {
console.error('Error:', error.message); // Сообщение об ошибке
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Примечание: В примере Node.js опция checkServerIdentity: () => { return null; }
удалена. Она отключает проверку имени сервера — это опасно. Ошибку OpenSSL unable to get local issuer certificate
решает указание доверенного CA через параметр ca
, а не отключение валидации имени хоста. Если появляется ошибка именно проверки имени, вероятно, проблема с сертификатом сервера, а не с хранилищем CA.
Пример 5: Использование OpenSSL с командой curl
curl
также поддерживает задавание файла CA через параметр --cacert
:
bash
# Запрос к HTTPS сайту с указанием файла CA от ServBay
# Выберите путь по вашей версии ServBay
# Для Apple Silicon:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# Для Intel:
# curl --cacert /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem https://example.com
1
2
3
4
5
6
7
2
3
4
5
6
7
При корректном CA-файле и валидном сертификате сервера не будет ни одной ошибки проверки сертификатов.
Итог
Ошибка 20:unable to get local issuer certificate
при установлении SSL/TLS-соединения через OpenSSL встречается очень часто. Её причина — отсутствие указания доверенного хранилища CA для проверки сертификата сервера. ServBay предоставляет готовый файл cacert.pem
с популярными корневыми сертификатами CA.
Для устранения данной ошибки в вашей среде разработки достаточно явно указать путь к файлу cacert.pem
от ServBay через настройки php.ini
, параметры SSL в вашем коде, а также опции команд openssl
и curl
. Обязательно используйте путь, подходящий к типу вашего процессора macOS (Apple Silicon или Intel) и выбранной версии OpenSSL в ServBay. При правильном задании доверенного CA ваше локальное окружение будет надёжно и безопасно взаимодействовать с SSL/TLS-сервисами и внешними ресурсами.