OpenSSL Probleemoplossing: De fout unable to get local issuer certificate
oplossen
Bij het maken van beveiligde verbindingen met OpenSSL (zoals netwerkverzoeken in PHP, het uitvoeren van openssl
- of curl
-opdrachten, of het initiëren van een SSL/TLS-verbinding in Node.js/Python-applicaties), kunnen ontwikkelaars de volgende foutmelding tegenkomen: 20:unable to get local issuer certificate
. Dit is een veelvoorkomend legacy-probleem met betrekking tot de manier waarop OpenSSL de certificaten van peers valideert.
Uit veiligheidsoverwegingen vereist OpenSSL standaard dat vertrouwde root Certificate Authorities (CA's) expliciet bekend zijn wanneer het een certificaatketen valideert. Als deze vertrouwde ankerpunten niet gevonden of opgegeven zijn, kan OpenSSL de geldigheid van het servercertificaat niet controleren en wordt deze fout gerapporteerd.
In dit artikel leggen we uit waardoor deze fout wordt veroorzaakt en bieden we oplossingen binnen de ServBay-omgeving, inclusief het configureren van de OpenSSL truststore voor PHP, Python, Node.js en de curl
-opdracht.
Foutmelding 20:unable to get local issuer certificate
Probleembeschrijving
Wanneer OpenSSL het SSL/TLS-certificaat van een externe server probeert te valideren, bouwt het een certificaatketen op vanaf het servercertificaat tot aan een vertrouwde root CA. Als OpenSSL lokaal één of meerdere tussencertificaten of het uiteindelijke rootcertificaat uit de keten niet kan vinden, óf als er geen juist geconfigureerde truststore is opgegeven (CAFile of CAPath), mislukt de validatie en verschijnt de foutmelding 20:unable to get local issuer certificate
.
Kortom: OpenSSL weet niet welke Certificate Authority het moet vertrouwen en kan daarom de identiteit van de server waarmee het verbinding probeert te maken niet bevestigen.
OpenSSL-versies en CA-certificaatpaden in ServBay
Als een geïntegreerde, lokale webontwikkelomgeving levert ServBay vooraf gerelateerde OpenSSL-pakketten en bevat het veelgebruikte publieke rootcertificaten, klaar voor ontwikkelaars. De OpenSSL-versie die ServBay gebruikt, hangt af van het type chip in uw apparaat:
- Apple Silicon (M-serie chips) ServBay: gebruikt OpenSSL versie 3.2.1.
- Intel-chip ServBay: gebruikt OpenSSL versie 1.1.1u.
De bijbehorende CA-certificaatbestanden (cacert.pem
) en certificaatmappen (certs
) zijn terug te vinden in het installatiepad van de ServBay-software. Zoek het juiste pad op basis van jouw versie van ServBay:
ini
# Bundelbestand met alle vertrouwde rootcertificaten
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Map met losse certificaatbestanden (meestal is cacert.pem voldoende, maar sommige applicaties kunnen capath vereisen)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# Bundelbestand met alle vertrouwde rootcertificaten
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Map met losse certificaatbestanden
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
De kernoplossing voor de fout unable to get local issuer certificate
is op de juiste plek de locatie van bovenstaande cafile
of capath
te specificeren, zodat OpenSSL weet waar de vertrouwde CA-certificaten staan.
Oplossingsvoorbeelden
Hieronder staan voorbeelden van hoe je de OpenSSL CA truststore specificeert binnen verschillende tools en programmeertalen.
Voorbeeld 1: Verbinding testen met het openssl
-commando
Als je een verbinding test met het OpenSSL s_client-commando en deze fout krijgt:
bash
openssl s_client -quiet -connect gmail.com:443
1
Dan krijg je mogelijk een foutmelding zoals hieronder, waarin verify error:num=20:unable to get local issuer certificate
voorkomt:
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
# ... overige verbindingsinformatie ...
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Oplossing:
Geef via de -CAfile
-optie expliciet het pad op naar het CA-certificaatbestand van 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
Na een succesvolle verbinding en validatie verdwijnt de verify error:num=20
regel uit de output en is verify return
altijd 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
# ... overige verbindingsinformatie ...
1
2
3
4
5
6
7
2
3
4
5
6
7
Voorbeeld 2: OpenSSL gebruiken in PHP
Veel netwerktaken in PHP (zoals file_get_contents
voor HTTPS-URL's, stream_socket_client
voor SSL-verbindingen, cURL-extensie, etc.) zijn afhankelijk van OpenSSL. Je kunt het pad naar de CA truststore specificeren via de php.ini
of in de code met stream context-opties.
Methode A: Bewerk php.ini
(aanbevolen)
Dit is de makkelijkste, globale oplossing. Open de php.ini
van je gebruikte PHP-versie (te vinden via het ServBay controlepaneel) en zoek het gedeelte [openssl]
. Voeg daar de volgende regels toe om openssl.cafile
en openssl.capath
te specificeren. Kies het juiste pad bij jouw chiptype.
ini
[openssl]
; Specificeer het gecertificeerde CA-bundelbestand
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Specificeer de directory met CA-certificaten (optioneel, aanbevolen)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[openssl]
; Specificeer het gecertificeerde CA-bundelbestand
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Specificeer de directory met CA-certificaten (optioneel, aanbevolen)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
Na bewerken van php.ini
, herstart de PHP service binnen ServBay (of herstart ServBay geheel) om de wijzigingen toe te passen.
Methode B: Configuratie toevoegen in de code (alleen voor huidige verbinding)
Als je de globale php.ini
niet wilt aanpassen, kun je bij het maken van een SSL/TLS verbinding met stream_context_create
het pad naar de CA-bundel opgeven via de 'ssl'
opties.
php
<?php
// Voorbeeld: Verbinden met een SMTP-server met SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Kies het juiste CA-certificaatpad volgens jouw ServBay-versie
// Voor Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Voor Intel:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // Schakel validatie van peer-certificaat in
'verify_peer_name' => true, // Controleer of de hostnaam van het certificaat overeenkomt
'allow_self_signed' => false, // Sta geen zelfondertekende certificaten toe (tenzij expliciet vertrouwd)
'cafile' => $caCertFile, // Geef CA-bundelbestand op
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Optioneel, directory voor CA-certificaten
],
];
$context = stream_context_create($contextOptions);
// Bouw SSL/TLS-verbinding op met opgegeven context
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // Timeout
STREAM_CLIENT_CONNECT,
$context // Contextopties
);
if ($connection) {
echo "Connection established to $server:$port\n";
// Voorbeeld: Stuur een EHLO-commando
fwrite($connection, "EHLO servbay.demo\r\n"); // Testdomein gerelateerd aan 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
Voorbeeld 3: OpenSSL in Python gebruiken (ssl-module)
Ook Python's ssl
-module laat je een SSL/TLS-context instellen en het CA-certificaat opgeven.
python
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Kies het juiste CA-certificaatpad volgens jouw ServBay-versie
# Voor Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# Voor Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# Maak een standaard SSL-context aan en geef het CA-bestand op
context = ssl.create_default_context(cafile=ca_cert_file)
# Je kunt ook een directory opgeven: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Normale socketverbinding opzetten
with socket.create_connection((server, port)) as sock:
# Zet socket om naar SSL-socket
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL connection established. Negotiated Protocol: {ssock.version()}")
# Voorbeeld: Stuur een EHLO-commando
ssock.sendall(b"EHLO servbay.demo\r\n") # Voorbeeld domeinnaam van 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
Voorbeeld 4: OpenSSL in Node.js gebruiken (tls-module)
Het tls
-module van Node.js is bedoeld voor TLS/SSL-verbindingen. Specificeer de trusted CA via het ca
-attribuut in de optieparameter, als string of Buffer-array. De eenvoudigste manier: lees het ServBay cacert.pem
-bestand in.
javascript
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Gebruik een betrouwbare, publiek toegankelijke server als voorbeeld
const port = 443;
// Kies het juiste CA-certificaatpad volgens jouw ServBay-versie
// Voor Apple Silicon:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Voor Intel:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// Lees de inhoud van het CA-certificaatbestand in
ca: fs.readFileSync(caCertFile),
// Node.js tls-module valideert standaard de hostnaam (checkServerIdentity)
// Als het CA-bestand correct is opgegeven en het servercertificaat geldig is, zal deze validatie slagen.
// Schakel checkServerIdentity niet uit tenzij je de risico's goed begrijpt!
// checkServerIdentity: () => { return null; } // <-- Vermijd het uitschakelen van deze belangrijke beveiligingscontrole!
};
const socket = tls.connect(options, () => {
console.log('SSL connection established');
// Voor HTTPS zou je hier een HTTP-request sturen; hier alleen een verbindingsvoorbeeld
// 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); // Toon foutmelding
});
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
Let op: In het Node.js-voorbeeld is checkServerIdentity: () => { return null; }
verwijderd. Deze optie schakelt de validatie van de serverhostname uit, wat zeer onveilig is. De OpenSSL-fout unable to get local issuer certificate
gaat over een ontbrekend trust anchor en verschilt van hostnamevalidatie (verify_peer_name
). Geef het ca
-option correct op en zorg dat het servercertificaat geldig is; dan verloopt hostnaamverificatie in Node.js standaard correct. Fouten bij hostnamevalidatie wijzen doorgaans op een fout in het servercertificaat zelf, niet op een probleem met de CA-truststore.
Voorbeeld 5: OpenSSL gebruiken in het curl
-commando
Ook curl
maakt gebruik van OpenSSL (of een andere SSL-library) voor HTTPS-verzoeken. Met de parameter --cacert
kun je het CA-certificaatbestand direct opgeven.
bash
# Gebruik het CA-certificaatbestand van ServBay om een HTTPS-site te benaderen
# Kies het juiste pad volgens jouw ServBay-versie
# Voor Apple Silicon:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# Voor 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
Heeft je CA-pad de juiste instelling en is het servercertificaat geldig, dan zal curl
de inhoud ophalen zonder certificaatfouten.
Samenvatting
De foutmelding 20:unable to get local issuer certificate
komt zeer regelmatig voor bij OpenSSL-verbindingen en wordt veroorzaakt doordat OpenSSL een truststore nodig heeft om servercertificaten te valideren. ServBay levert een vooraf gepackaged cacert.pem
-bestand vol veelgebruikte publieke root CA-certificaten.
De oplossing is: geef in jouw ontwikkelomgeving (via php.ini
, SSL-context-opties in je code, commando-parameters als openssl -CAfile
of curl --cacert
) het ServBay cacert.pem
-bestand op. Vergeet niet het juiste pad te selecteren afhankelijk van je macOS-chiptype (Apple Silicon of Intel) en de juiste ServBay OpenSSL-versie. Door je CA truststore goed te configureren, kun je veilig verbinding maken met externe SSL/TLS-diensten vanuit je lokale ontwikkelomgeving.