Rozwiązywanie problemów z OpenSSL: jak naprawić błąd unable to get local issuer certificate
Podczas korzystania z OpenSSL do realizowania bezpiecznych połączeń (np. wysyłając żądania sieciowe w PHP, uruchamiając polecenia openssl
lub curl
, czy nawiązując połączenia SSL/TLS w aplikacjach Node.js/Python) deweloperzy mogą napotkać komunikat o błędzie: 20:unable to get local issuer certificate
. To częsty, historyczny problem związany z tym, jak OpenSSL weryfikuje certyfikaty stron trzecich.
Ze względów bezpieczeństwa, domyślnie OpenSSL wymaga jasno określonego, zaufanego urzędu certyfikacji (CA) podczas weryfikacji łańcucha certyfikatów. Jeśli odpowiedni root CA nie zostanie znaleziony lub nie zostanie wskazany, nie jest możliwe potwierdzenie autentyczności certyfikatu serwera, co skutkuje opisanym błędem.
W tym artykule szczegółowo wyjaśniamy przyczynę tego błędu i prezentujemy rozwiązania dedykowane środowisku ServBay, włączając konfigurację OpenSSL dla PHP, Pythona, Node.js oraz komendę curl
.
Błąd 20:unable to get local issuer certificate
Opis problemu
Gdy OpenSSL próbuje zweryfikować certyfikat SSL/TLS zdalnego serwera, buduje łańcuch certyfikatów prowadzący od certyfikatu serwera aż do zaufanego root CA. Jeżeli lokalnie nie ma niezbędnych certyfikatów pośrednich lub głównego certyfikatu CA, lub jeśli nie zdefiniowano ścieżki do zaufanego magazynu certyfikatów (CAFile
lub CAPath
), weryfikacja zakończy się niepowodzeniem i pojawi się błąd 20:unable to get local issuer certificate
.
Mówiąc prościej: OpenSSL nie wie, któremu urzędowi certyfikacji zaufać, a co za tym idzie — nie może potwierdzić tożsamości serwera, z którym się łączy.
Wersje OpenSSL i ścieżki do certyfikatów CA w ServBay
ServBay jako zintegrowane lokalne środowisko web developerskie dostarcza pakiet OpenSSL wraz z domyślnym zbiorem powszechnie wykorzystywanych certyfikatów root CA. Wersja OpenSSL w ServBay zależy od rodzaju procesora w Twoim komputerze:
- ServBay na Apple Silicon (seria M): korzysta z OpenSSL 3.2.1.
- ServBay na procesorach Intel: korzysta z OpenSSL 1.1.1u.
Powiązane z tymi wersjami OpenSSL pliki certyfikatów (cacert.pem
) oraz foldery z certyfikatami (certs
) znajdują się w katalogu instalacyjnym ServBay. Odszukaj właściwe ścieżki zależnie od swojej wersji ServBay:
ini
# Zbiorczy plik wszystkich zaufanych certyfikatów root
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Katalog z pojedynczymi plikami certyfikatów (zwykle cacert.pem wystarcza, ale niektóre aplikacje wymagają capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# Zbiorczy plik wszystkich zaufanych certyfikatów root
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Katalog z pojedynczymi plikami certyfikatów
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
Kluczowe przy rozwiązywaniu błędu unable to get local issuer certificate
jest jawne wskazanie lokalizacji pliku cafile
lub folderu capath
, aby przekazać OpenSSL informację, gdzie szukać zaufanych certyfikatów CA.
Przykłady rozwiązań
Poniżej znajdziesz przykłady tego, jak w różnych narzędziach i środowiskach programistycznych ustawić lokalizację certyfikatów zaufanych dla OpenSSL.
Przykład 1: Testowanie połączenia poleceniem openssl
Jeśli podczas testowania połączenia poleceniem openssl s_client
pojawia się błąd:
bash
openssl s_client -quiet -connect gmail.com:443
1
Możesz zobaczyć następujące ostrzeżenia z 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
# ... inne informacje o połączeniu ...
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Rozwiązanie:
Doprecyzuj ścieżkę do pliku z zaufanymi certyfikatami CA ServBay przez parametr -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
Przy pomyślnym połączeniu i poprawnej weryfikacji certyfikatów, w wyjściu nie znajdziesz już linii verify error:num=20
, a każdy verify return
powinien mieć wartość 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
# ... inne informacje o połączeniu ...
1
2
3
4
5
6
7
2
3
4
5
6
7
Przykład 2: Korzystanie z OpenSSL w PHP
Wiele funkcji sieciowych w PHP (np. file_get_contents
po HTTPS, stream_socket_client
z SSL, rozszerzenie cURL) działa w oparciu o OpenSSL. Lokalizację zaufanych certyfikatów możesz wskazać globalnie w php.ini
lub bezpośrednio w kodzie przez opcje kontekstu stream
.
Metoda A: Edycja pliku php.ini
(zalecana)
To najprostsze rozwiązanie na poziomie całego środowiska. Otwórz php.ini
używanej wersji PHP (możesz go zlokalizować z panelu sterowania ServBay), znajdź sekcję [openssl]
i ustaw odpowiednie ścieżki do openssl.cafile
oraz (opcjonalnie) openssl.capath
. Dostosuj lokalizację do swojego procesora.
ini
[openssl]
; Ścieżka do wiązki zaufanych certyfikatów CA
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Katalog z certyfikatami CA (opcjonalny, ale zalecany)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[openssl]
; Ścieżka do wiązki zaufanych certyfikatów CA
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Katalog z certyfikatami CA (opcjonalny, ale zalecany)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
Po zapisaniu zmian zrestartuj usługę PHP w ServBay (lub całą aplikację), aby konfiguracja została załadowana.
Metoda B: Wskazanie pliku CA w kodzie (wpływa tylko na bieżące połączenie)
Bez zmiany globalnej konfiguracji, możesz podać ścieżkę do pliku CA tworząc kontekst połączenia przy stream_context_create
:
php
<?php
// Przykład: nawiązanie połączenia SSL/TLS z serwerem SMTP
$server = 'ssl0.ovh.net';
$port = 465;
// Dostosuj ścieżkę do wersji 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, // Weryfikuj certyfikat serwera
'verify_peer_name' => true, // Sprawdzaj zgodność nazwy hosta
'allow_self_signed' => false, // Nie dopuszczaj własnych, niezaufanych certyfikatów
'cafile' => $caCertFile, // Ścieżka do pliku CA
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Opcjonalnie: katalog CA
],
];
$context = stream_context_create($contextOptions);
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // Timeout
STREAM_CLIENT_CONNECT,
$context
);
if ($connection) {
echo "Połączenie nawiązane z $server:$port\n";
// Przykład: wysyłanie komendy EHLO
fwrite($connection, "EHLO servbay.demo\r\n");
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Nie udało się połączyć z $server:$port. Błąd: $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
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
Przykład 3: Użycie OpenSSL w Pythonie (moduł ssl)
Moduł ssl
w Pythonie pozwala tworzyć własny kontekst SSL/TLS i jawnie podać plik CA.
python
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Dostosuj ścieżkę do wersji 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'
# Stwórz domyślny kontekst SSL ze wskazanym plikiem CA
context = ssl.create_default_context(cafile=ca_cert_file)
# Możesz też podać katalog: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
with socket.create_connection((server, port)) as sock:
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"Nawiązano połączenie SSL. Negocjowany protokół: {ssock.version()}")
# Przykład: wysyłanie EHLO
ssock.sendall(b"EHLO servbay.demo\r\n")
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Nie udało się połączyć lub błąd 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
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
Przykład 4: Użycie OpenSSL w Node.js (moduł tls)
Moduł tls
w Node.js do połączeń SSL/TLS przetwarza zaufany CA przez opcję ca
. Najprościej: wczytaj zawartość pliku cacert.pem
z ServBay.
javascript
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Przykład z wiarygodnym serwerem
const port = 443;
// Dostosuj ścieżkę do wersji 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,
// Wczytaj plik z certyfikatami CA
ca: fs.readFileSync(caCertFile),
// Node.js domyślnie sprawdza nazwę hosta (checkServerIdentity)
// Prawidłowy plik CA i ważność certyfikatu gwarantują prawidłową weryfikację.
// Nie wyłączaj checkServerIdentity bez wyraźnej potrzeby i świadomości ryzyka!
};
const socket = tls.connect(options, () => {
console.log('Połączenie SSL nawiązane');
// Dla HTTPS zwykle wysyła się zapytanie HTTP, tu tylko demo połączenia
// 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('Połączenie zakończone');
});
socket.on('error', (error) => {
console.error('Błąd:', 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
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
Uwaga: Przykład w Node.js celowo nie zawiera opcji checkServerIdentity: () => { return null; }
— jej użycie drastycznie obniża bezpieczeństwo! Błąd OpenSSL unable to get local issuer certificate
dotyczy zaufania do root CA, a nie weryfikacji nazw hostów. Po prawidłowym wskazaniu opcji ca
oraz ważnym certyfikacie serwera domyślna weryfikacja hosta zadziała poprawnie. Gdy pojawiają się błędy hosta, przyczyną najczęściej jest problem z samym certyfikatem strony, nie zaś z pytaniem o root CA.
Przykład 5: Wskazanie CA w poleceniu curl
Narzędzie curl
wykorzystuje OpenSSL (lub inną bibliotekę SSL) do nawiązywania połączeń HTTPS. Ścieżkę do pliku CA najlepiej wskazać przez opcję --cacert
:
bash
# Pobierz stronę HTTPS wskazując plik CA z 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
2
3
4
5
6
Poprawnie ustawiona ścieżka do CA i ważny certyfikat na serwerze zapewnią brak błędów walidacji certyfikatów w wyjściu curl
.
Podsumowanie
Błąd 20:unable to get local issuer certificate
często pojawia się przy łączeniu SSL/TLS z udziałem OpenSSL, ponieważ biblioteka ta wymaga wybrania zaufanego magazynu CA dla weryfikacji certyfikatu serwera. ServBay dostarcza preinstalowany plik cacert.pem
zawierający zbiór powszechnie stosowanych root CA.
Aby rozwiązać ten błąd, w swoim środowisku developerskim (w pliku php.ini
, poprzez opcje SSL w kodzie, przez parametry linii komend np. -CAfile
w openssl
lub --cacert
w curl
) wskaż właściwy plik cacert.pem
z ServBay. Pamiętaj o dopasowaniu ścieżki do swojej wersji ServBay i platformy (Apple Silicon lub Intel). Dzięki temu umożliwisz bezpieczną wymianę danych z zewnętrznymi usługami SSL/TLS w lokalnym środowisku developerskim.