Усунення несправностей 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) для перевірки ланцюжка сертифікатів. Якщо ці довірчі «якорі» не вказані або відсутні, він не зможе підтвердити легітимність серверного сертифіката й відповідно повідомить про помилку.
У цій статті детально пояснюється суть цієї помилки та описуються рішення для ServBay: як правильно налаштувати довірені сертифікати OpenSSL у PHP, Python, Node.js й для команди curl
.
Помилка 20:unable to get local issuer certificate
Опис проблеми
Коли OpenSSL намагається перевірити SSL/TLS-сертифікат віддаленого сервера, він створює ланцюжок сертифікатів від серверного до довіреного кореневого сертифіката CA. Якщо OpenSSL не знаходить локально один з необхідних проміжних чи кореневих сертифікатів або не виявляє коректно налаштоване сховище довіри (CAFile чи CAPath), перевірка провалюється й повертає помилку 20:unable to get local issuer certificate
.
Простіше кажучи, OpenSSL не знає, якому центру сертифікації довіряти, тому не може підтвердити ідентичність сервера, з яким здійснюється з'єднання.
Версія OpenSSL і шляхи до CA-сертифікатів у ServBay
ServBay — це інтегроване локальне середовище веб-розробки, що має вбудований OpenSSL і комплект загальнодоступних кореневих сертифікатів CA для зручності розробників. Версія OpenSSL у ServBay залежить від типу процесора вашого пристрою:
- Apple Silicon (чипи серії M): використовується OpenSSL версії 3.2.1.
- Intel: використовується OpenSSL версії 1.1.1u.
Відповідні файли з CA-сертифікатами (cacert.pem
) та директорія з сертифікатами (certs
) розташовані у директорії пакунків ServBay. Залежно від вашої версії 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
Суть вирішення помилки unable to get local issuer certificate
полягає в тому, щоб явно вказати OpenSSL, де шукати довірені сертифікати CA через параметри cafile
або capath
у кожному випадку використання.
Приклади рішень
Ось приклади, як вказати CA-сховище для OpenSSL в різних інструментах та мовах програмування.
Приклад 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
Вирішення:
Вкажіть повний шлях до файлу CA від ServBay через параметр -CAfile
:
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 return
буде дорівнювати 1
, і рядків із verify error:num=20
вже не буде:
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. Ви можете або вказати CA у файлі php.ini
, або безпосередньо у коді через опції контексту stream.
Метод A: Редагування php.ini
(рекомендовано)
Це найзручніше глобальне рішення. Відкрийте відповідний до вашої версії PHP файл php.ini
(у ServBay є зручний інтерфейс для його редагування), знайдіть секцію [openssl]
і додайте або змініть наступні рядки, вказавши свій шлях до CA-сертифіката залежно від типу чипа:
ini
[openssl]
; Вказати файл-збірку довірених CA
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Вказати директорію з CA-сертифікатами (не обов'язково, але рекомендується)
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
; Вказати директорію з CA-сертифікатами (не обов'язково, але рекомендується)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
Після змін у php.ini
перезапустіть PHP-сервіс у ServBay або весь ServBay для застосування конфігурації.
Метод B: Вказівка у коді (лише для поточного з'єднання)
Якщо не хочете змінювати глобальні налаштування, вкажіть шлях до CA-файлу через опції при створенні контексту SSL/TLS:
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', // Необов'язково
],
];
$context = stream_context_create($contextOptions);
// Встановити SSL/TLS-з'єднання з використанням контексту
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // timeout
STREAM_CLIENT_CONNECT,
$context
);
if ($connection) {
echo "Підключення встановлено до $server:$port\n";
// Приклад: команда EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // тестовий домен із брендом ServBay
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Не вдалося підключитися до $server:$port. Помилка: $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)
Модуль ssl
у Python дозволяє створити 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:
# Встановити просте socket-з'єднання
with socket.create_connection((server, port)) as sock:
# Обернути звичайний socket у SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL-з'єднання встановлено. Зафіксовано протокол: {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"Помилка підключення або SSL: {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: Використання OpenSSL у Node.js (модуль tls)
Модуль tls
у Node.js забезпечує TLS/SSL-з'єднання. Вкажіть довірені CA-сертифікати через опцію ca
, зачитавши вміст відповідного файлу cacert.pem
від 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),
// Перевірка імені хоста активна за замовчуванням — не вимикайте!
// checkServerIdentity: () => { return null; } // <-- Не використовуйте це. Це небезпечно!
};
const socket = tls.connect(options, () => {
console.log('SSL-з\'єднання встановлено');
// Як приклад: для 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('З\'єднання закрито');
});
socket.on('error', (error) => {
console.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
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
Увага: У прикладі Node.js прибрано параметр checkServerIdentity: () => { return null; }
, адже він повністю вимикає перевірку імені хоста — це небезпечно. Помилка unable to get local issuer certificate
стосується саме довіри до CA, а не перевірки імені сервера. Якщо CA вказано правильно, і сервер має валідний сертифікат, перевірка імені має проходити успішно за замовчуванням.
Приклад 5: Використання OpenSSL у команді curl
curl
також використовує OpenSSL (або іншу SSL-бібліотеку) для HTTPS-запитів. Шлях до CA вказується через параметр --cacert
:
bash
# Використання CA-файлу від ServBay для підключення до HTTPS-сайту
# Для 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
2
3
4
5
6
Якщо все налаштовано правильно, curl
отримає відповідь із сайту без помилок перевірки сертифіката.
Висновок
Помилка 20:unable to get local issuer certificate
дуже типова для підключень SSL/TLS через OpenSSL. Її суть полягає у необхідності чітко вказати OpenSSL довірене сховище CA для підтвердження серверного сертифіката. У ServBay розробникам вже доступний файл cacert.pem
, що містить найпоширеніші публічні root CA.
Вирішення — це додавання шляху до файлу cacert.pem
від ServBay через відповідні параметри: в php.ini
, в опціях SSL-контексту у коді, через параметр командного рядка (-CAfile
для openssl
, --cacert
для curl
). Важливо уважно вибирати шлях відповідно до архітектури macOS (Apple Silicon або Intel) та версії OpenSSL. Правильно вказавши CA, ви забезпечите безпечний обмін даними у вашому локальному середовищі розробки з зовнішніми SSL/TLS-сервісами.