Rozwiązywanie problemów z OpenSSL: jak usunąć błąd unable to get local issuer certificate
Podczas nawiązywania bezpiecznego połączenia za pomocą OpenSSL (np. podczas wykonywania zapytań sieciowych w PHP, korzystania z poleceń openssl
lub curl
, czy budowania połączeń SSL/TLS w aplikacjach Node.js/Python) programiści często spotykają się z błędem 20:unable to get local issuer certificate
. Jest to powszechny, historyczny problem powiązany z procesem weryfikacji certyfikatów przez OpenSSL.
Ze względów bezpieczeństwa, domyślnie OpenSSL podczas weryfikowania łańcucha certyfikatów wymaga dokładnie określonego, zaufanego urzędu certyfikacji (CA - Certificate Authority). Jeśli taki CA nie zostanie odnaleziony lub poprawnie wskazany, OpenSSL nie jest w stanie zweryfikować autentyczności certyfikatu serwera i raportuje ten błąd.
Ten przewodnik wyjaśnia szczegółowo przyczyny błędu oraz opisuje, jak rozwiązać ten problem w środowisku ServBay, w tym jak skonfigurować zaufane CA dla PHP, Pythona, Node.js oraz polecenia curl
.
Komunikat błędu 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 CA. Jeśli OpenSSL nie znajdzie na lokalnym komputerze niezbędnych certyfikatów pośrednich lub końcowego certyfikatu CA, albo nie został poprawnie skonfigurowany odpowiedni magazyn zaufanych certyfikatów (CAFile lub CAPath), proces weryfikacji kończy się niepowodzeniem i pojawia się błąd 20:unable to get local issuer certificate
.
W skrócie – OpenSSL nie wie, której instytucji certyfikującej zaufać i przez to nie potrafi potwierdzić tożsamości serwera.
Wersje OpenSSL i lokalizacja certyfikatów CA w ServBay
ServBay to zintegrowane lokalne środowisko programistyczne dla WWW, które zawiera preinstalowane paczki OpenSSL oraz zestaw popularnych zaufanych certyfikatów CA, aby ułatwić pracę deweloperom. Wersja OpenSSL używana w ServBay zależy od typu procesora na Twoim urządzeniu:
- ServBay na Apple Silicon (procesory M): korzysta z OpenSSL w wersji 3.2.1.
- ServBay na Intel: korzysta z OpenSSL w wersji 1.1.1u.
Odpowiadające tym wersjom pliki certyfikatów CA (cacert.pem
) oraz katalogi z certyfikatami (certs
) znajdują się w ścieżce instalacyjnej oprogramowania ServBay. Aby znaleźć odpowiednie miejsce, sprawdź swoją platformę i architekturę:
ini
# Pakiet z wszystkimi zaufanymi certyfikatami root CA
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Katalog z oddzielnymi plikami certyfikatów (najczęściej wystarczy cacert.pem, ale czasem aplikacje oczekują capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# Pakiet z wszystkimi zaufanymi certyfikatami root CA
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Katalog z oddzielnymi plikami certyfikatów
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
ini
# Pakiet z wszystkimi zaufanymi certyfikatami root CA
cafile=C:\ServBay\package\common\openssl\3.3\cacert.pem
# Katalog z oddzielnymi plikami certyfikatów
capath=C:\ServBay\package\common\openssl\3.3\certs
1
2
3
4
2
3
4
Kluczowym rozwiązaniem dla błędu unable to get local issuer certificate
jest wskazanie odpowiednich ścieżek do cafile
lub capath
w miejscach, gdzie korzysta się z OpenSSL – tym samym informując, gdzie znajdują się zaufane certyfikaty CA.
Przykłady rozwiązań
Poniżej znajdziesz przykłady jak skonfigurować zaufany magazyn CA dla OpenSSL w różnych narzędziach i środowiskach programistycznych.
Przykład 1: testowanie połączenia za pomocą komendy openssl
Jeżeli podczas testowania połączenia poleceniem openssl s_client
występuje błąd:
bash
openssl s_client -quiet -connect gmail.com:443
1
Zwrócony może być komunikat, w którym widnieje 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:
Skorzystaj z parametru -CAfile
, wskazując na odpowiedni plik z certyfikatami CA dostarczony przez 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
Po poprawnym nawiązaniu połączenia i weryfikacji, wynik powinien zawierać tylko verify return:1
bez 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
# ... inne informacje o połączeniu ...
1
2
3
4
5
6
7
2
3
4
5
6
7
Przykład 2: użycie OpenSSL w PHP
Wiele funkcji sieciowych w PHP (np. file_get_contents
do HTTPS, stream_socket_client
dla SSL, rozszerzenie cURL) korzysta z OpenSSL. Możesz wskazać zaufany CA globalnie w php.ini
lub lokalnie w kodzie poprzez stream context.
Metoda A: Edycja globalnego pliku php.ini
(zalecane)
Jest to najwygodniejsze, globalne rozwiązanie. Edytuj plik php.ini
przypisany do używanej wersji PHP (możesz go otworzyć przez panel sterowania ServBay), znaleźdź sekcję [openssl]
i dodaj lub zmodyfikuj poniższe wpisy, podając właściwą ścieżkę zależnie od rodzaju procesora.
ini
[openssl]
; Ścieżka do pliku z zaufanymi certyfikatami CA
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Opcjonalnie: katalog z certyfikatami CA (zalecane ustawienie)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[openssl]
; Ścieżka do pliku z zaufanymi certyfikatami CA
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Opcjonalnie: katalog z certyfikatami CA
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
Po zapisaniu zmian w php.ini
należy zrestartować usługę PHP w ServBay (lub cały ServBay), aby konfiguracja została wczytana.
Metoda B: Ustawienie contextu w kodzie (tylko dla bieżącego połączenia)
Jeśli nie chcesz zmieniać ustawień globalnych, możesz przekazać ścieżkę do CA poprzez stream context podczas tworzenia połączenia SSL/TLS:
php
<?php
// Przykład połączenia z SMTP przez SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Wybierz właściwą ścieżkę do pliku CA dla Twojej 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, // Weryfikacja certyfikatu serwera
'verify_peer_name' => true, // Sprawdzenie zgodności nazwy hosta w certyfikacie
'allow_self_signed' => false, // Nie zezwalaj na samopodpisane certyfikaty, chyba że im ufasz
'cafile' => $caCertFile, // Ścieżka do pliku CA
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // opcjonalnie, katalog CA
],
];
$context = stream_context_create($contextOptions);
// Inicjacja połączenia SSL/TLS
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // timeout połączenia
STREAM_CLIENT_CONNECT,
$context // przekazanie contextu
);
if ($connection) {
echo "Connection established to $server:$port\n";
// Przykład wysłania komendy EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // użyta przykładowa domena 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
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
Przykład 3: zastosowanie OpenSSL w Pythonie (moduł ssl)
W module Python ssl
można utworzyć kontekst SSL/TLS i wskazać zaufany plik CA.
python
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Dopasuj ścieżkę do pliku CA 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'
# Tworzenie domyślnego kontekstu SSL ze wskazanym CA
context = ssl.create_default_context(cafile=ca_cert_file)
# Można też podać katalog: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Połączenie przez zwykły socket
with socket.create_connection((server, port)) as sock:
# Owijamy socket SSL-em
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL connection established. Negotiated Protocol: {ssock.version()}")
# Przykład wysłania EHLO
ssock.sendall(b"EHLO servbay.demo\r\n") # przykładowa domena 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
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
Przykład 4: Node.js i OpenSSL (moduł tls)
W Node.js do obsługi SSL/TLS używa się modułu tls
, w którym w opcjach połączenia możesz ustawić parametr ca
z zawartością pliku CA. Najprościej odczytać plik cacert.pem
przygotowany przez ServBay.
javascript
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // standardowa, zaufana domena jako przykład
const port = 443;
// Po odpowiednim dopasowaniu do Twojego środowiska ServBay:
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,
// Ładowanie zawartości pliku CA
ca: fs.readFileSync(caCertFile),
// Domyślnie moduł tls w Node.js sprawdza tożsamość hosta (checkServerIdentity),
// Jeśli podasz prawidłowy plik CA i serwer ma poprawny certyfikat, weryfikacja zakończy się sukcesem.
// Nie wyłączaj checkServerIdentity bez ważnego powodu!
// checkServerIdentity: () => { return null; } // <-- NIE używaj tej linii, dezaktywuje krytyczną weryfikację!
};
const socket = tls.connect(options, () => {
console.log('SSL connection established');
// Zazwyczaj dla HTTPS wysyła się żądanie HTTP, tutaj tylko przykład ustanowienia 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('Connection closed');
});
socket.on('error', (error) => {
console.error('Error:', error.message); // Wyświetla komunikat błędu
});
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: W przykładzie Node.js celowo pominięto checkServerIdentity: () => { return null; }
, ponieważ wyłącza on weryfikację tożsamości hosta, co jest niebezpieczne. Błąd OpenSSL unable to get local issuer certificate
dotyczy zaufania CA, a nie sprawdzania nazwy hosta (verify_peer_name
). Prawidłowo wskazany plik CA i poprawne certyfikaty gwarantują sukces weryfikacji domyślnej w Node.js. Jeśli występują błędy weryfikacji nazwy hosta, zwykle są związane z zawartością certyfikatu, a nie z CA.
Przykład 5: użycie curl
z OpenSSL
Polecenie curl
pozwala na wysyłanie zapytań HTTPS z wykorzystaniem wskazanego pliku CA (lub innych bibliotek SSL). Użyj parametru --cacert
:
bash
# Dostęp do strony HTTPS z wykorzystaniem pliku CA z ServBay
# Wybierz odpowiednią ścieżkę dla Twojej wersji 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
Prawidłowo wskazany plik CA i ważny certyfikat serwera gwarantują brak błędu weryfikacji certyfikatu.
Podsumowanie
Błąd 20:unable to get local issuer certificate
podczas korzystania z OpenSSL jest bardzo powszechny i wynika z braku wskazania magazynu zaufanych certyfikatów CA wymaganego podczas weryfikacji certyfikatów serwera. ServBay dostarcza gotowy plik z popularnymi certyfikatami root CA – cacert.pem
.
Sposób naprawy polega na wskazaniu ścieżki do pliku cacert.pem
dostarczonego przez ServBay (w php.ini
, w opcjach połączenia SSL w kodzie, w parametrach poleceń takich jak openssl -CAfile
czy curl --cacert
). Pamiętaj, by zawsze dobrać odpowiednią ścieżkę do pliku zależnie od architektury Twojego komputera (Apple Silicon vs Intel) oraz właściwej wersji OpenSSL w ServBay.
Poprawna konfiguracja zaufanych certyfikatów CA umożliwia bezpieczną komunikację lokalnego środowiska developerskiego z zewnętrznymi usługami SSL/TLS.