Risoluzione problemi OpenSSL: Correggere l’errore unable to get local issuer certificate
Quando si utilizza OpenSSL per connessioni sicure (ad esempio durante richieste di rete da ambiente PHP, esecuzione di comandi openssl
o curl
, o stabilendo connessioni SSL/TLS in applicazioni Node.js/Python), gli sviluppatori possono incontrare l’errore 20:unable to get local issuer certificate
. Si tratta di un problema comune e storico relativo a come OpenSSL verifica i certificati delle controparti.
Per motivi di sicurezza, OpenSSL richiede per impostazione predefinita la conoscenza esplicita delle autorità di certificazione (CA) root affidabili durante la verifica della catena di certificati. Se questi trust anchor non vengono trovati o specificati, OpenSSL non può confermare la validità del certificato del server e segnala di conseguenza un errore.
In questa guida spieghiamo in dettaglio le cause di questo errore e illustriamo come risolverlo nell’ambiente ServBay, includendo la configurazione dello store di trust OpenSSL per PHP, Python, Node.js e tramite comandi curl
.
Errore 20:unable to get local issuer certificate
Descrizione del problema
Quando OpenSSL tenta di verificare il certificato SSL/TLS di un server remoto, costruisce una catena di certificati dal certificato del server fino a una CA root affidabile. Se non riesce a trovare in locale uno qualsiasi dei certificati intermedi o la CA root finale, o se manca una trust store configurata (CAFile o CAPath) dove cercare questi certificati, il processo di verifica fallisce e restituisce l’errore 20:unable to get local issuer certificate
.
In sintesi, OpenSSL non sa a quale authority di certificazione affidarsi, quindi non può autenticare in sicurezza l’identità del server a cui si sta collegando.
Versione OpenSSL e percorso certificati CA in ServBay
ServBay è un ambiente integrato per lo sviluppo Web locale che include OpenSSL e una raccolta aggiornata delle CA root pubbliche più comuni, semplificando il lavoro agli sviluppatori. La versione OpenSSL utilizzata da ServBay dipende dall’architettura del tuo Mac:
- ServBay per Apple Silicon (chip serie M): OpenSSL versione 3.2.1.
- ServBay per chip Intel: OpenSSL versione 1.1.1u.
I file di certificato CA associati (cacert.pem
) e le directory dei certificati (certs
) si trovano nella cartella di installazione dei pacchetti ServBay. Individua il percorso in base alla tua versione ServBay:
# File bundle con tutte le CA root affidabili
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Directory con certificati CA separati (di solito cacert.pem è sufficiente, capath solo se richiesto da alcune applicazioni)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
# File bundle con tutte le CA root affidabili
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Directory con certificati CA separati
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
La soluzione all'errore unable to get local issuer certificate
consiste nell’indicare a OpenSSL, ovunque sia utilizzato, il percorso del file cafile
o della cartella capath
di cui sopra, così da specificare dove trovare le CA affidabili.
Esempi di soluzione
Di seguito trovi esempi di come indicare lo store di CA a OpenSSL nei vari strumenti e linguaggi.
Esempio 1: Collegamento di test con comando openssl
Se usando il comando openssl s_client
ricevi l’errore:
openssl s_client -quiet -connect gmail.com:443
Potresti visualizzare un output simile, che include verify error:num=20:unable to get local issuer certificate
:
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
# ... altre informazioni di connessione ...
2
3
4
5
6
7
8
Soluzione:
Specifica manualmente il percorso del file CA bundle fornito da ServBay tramite il parametro -CAfile
:
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/3.2/cacert.pem
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
Se la connessione e la verifica del certificato vanno a buon fine, nelle righe di output il valore di verify return
sarà 1
e non comparirà più la riga con l’errore verify error:num=20
:
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
# ... altre informazioni di connessione ...
2
3
4
5
6
7
Esempio 2: Uso di OpenSSL in PHP
Molte funzionalità di rete PHP (come file_get_contents
su URL HTTPS, stream_socket_client
per connessioni SSL, l’estensione cURL, ecc.) fanno affidamento su OpenSSL. Puoi specificare le CA di trust modificando il file php.ini
oppure tramite il context di stream nelle singole chiamate.
Metodo A: Modifica di php.ini
(consigliato)
Questo è il modo più semplice per applicare la configurazione globalmente. Modifica il file php.ini
della versione PHP attiva (lo trovi facilmente dal pannello di controllo di ServBay), cerca la sezione [openssl]
e aggiungi/modifica le seguenti righe per impostare openssl.cafile
e openssl.capath
usando il percorso corretto per il tuo chip.
[openssl]
; Percorso del file bundle con CA affidabili
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Directory delle CA (opzionale ma consigliata)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
5
[openssl]
; Percorso del file bundle con CA affidabili
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Directory delle CA (opzionale ma consigliata)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
5
Dopo aver modificato php.ini
, riavvia il servizio PHP di ServBay (o l’intero ServBay) perché le modifiche abbiano effetto.
Metodo B: Configurazione nel codice (solo per la connessione corrente)
Se non vuoi modificare il file php.ini
per tutte le applicazioni, puoi specificare il path al file CA direttamente quando crei il contesto SSL tramite stream_context_create
:
<?php
// Esempio: Connessione a un server SMTP via SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Scegli il percorso giusto per la tua versione di ServBay
// Per Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Per Intel:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // Abilita la verifica del certificato del server
'verify_peer_name' => true, // Verifica che il nome host corrisponda a quello nel certificato
'allow_self_signed' => false, // Blocca i certificati self-signed non esplicitamente affidabili
'cafile' => $caCertFile, // Specifica il file CA bundle
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Opzionale
],
];
$context = stream_context_create($contextOptions);
// Crea la connessione SSL/TLS usando il contesto creato
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // Timeout
STREAM_CLIENT_CONNECT,
$context // Passa le opzioni del contesto
);
if ($connection) {
echo "Connessione stabilita a $server:$port\n";
// Esempio: invio comando EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // Dominio di esempio per ServBay
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Impossibile collegarsi a $server:$port. Errore: $errstr ($errno)\n";
}
?>
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
Esempio 3: OpenSSL in Python (modulo ssl)
Il modulo ssl
di Python consente di creare un contesto SSL/TLS specificando i certificati CA di fiducia.
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Scegli il percorso giusto per la tua versione di ServBay
# Per Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# Per Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# Crea un contesto SSL predefinito, specificando il file CA
context = ssl.create_default_context(cafile=ca_cert_file)
# O in alternativa: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Crea una connessione socket normale
with socket.create_connection((server, port)) as sock:
# Wrappa la socket in una SSL socket
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"Connessione SSL stabilita. Protocollo negoziato: {ssock.version()}")
# Esempio: invia comando EHLO
ssock.sendall(b"EHLO servbay.demo\r\n") # Dominio di esempio ServBay
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Impossibile collegarsi o errore SSL: {e}")
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
Esempio 4: OpenSSL in Node.js (modulo tls)
Il modulo tls
di Node.js permette la creazione di connessioni TLS/SSL. Puoi specificare le CA di fiducia tramite la proprietà ca
nelle opzioni di connessione, leggendo direttamente il file cacert.pem
fornito da ServBay.
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Sito affidabile di esempio
const port = 443;
// Scegli il percorso giusto per la tua versione di ServBay
// Per Apple Silicon:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Per Intel:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// Legge il contenuto del file CA
ca: fs.readFileSync(caCertFile),
// Il modulo tls di Node.js effettua anche il controllo del nome host (checkServerIdentity) di default;
// se il file CA è corretto e il certificato del server è valido, la verifica andrà a buon fine.
// Non disabilitare checkServerIdentity a meno che tu non sia consapevole dei rischi!
// checkServerIdentity: () => { return null; } // <-- Non usare: disabilita controlli di sicurezza importanti!
};
const socket = tls.connect(options, () => {
console.log('Connessione SSL stabilita');
// Per HTTPS sarebbe necessario inviare una richiesta HTTP, questo è solo un esempio di handshake
// 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('Connessione chiusa');
});
socket.on('error', (error) => {
console.error('Errore:', error.message); // Messaggio di errore
});
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
Nota: Nell’esempio Node.js non viene usato checkServerIdentity: () => { return null; }
poiché questo disabilita la verifica del nome host e compromette la sicurezza. L’errore OpenSSL unable to get local issuer certificate
riguarda il trust nella root CA, non il controllo del nome host (verify_peer_name
). Impostando correttamente l’opzione ca
e usando certificati server validi, la verifica va a buon fine senza modificare checkServerIdentity
. Se si verifica un errore di host name, la causa è in genere nel certificato server, non nello store di trust CA.
Esempio 5: Utilizzo di curl con OpenSSL
Il comando curl
usa OpenSSL (o un’altra libreria SSL) per le richieste HTTPS. Puoi specificare il file CA bundle con il parametro --cacert
.
# Usa il file CA fornito da ServBay per accedere a un sito HTTPS
# Percorso per Apple Silicon:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# Per Intel:
# curl --cacert /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem https://example.com
2
3
4
5
6
Se specifichi il path corretto del certificato CA e il server ha un certificato valido, curl
scaricherà correttamente il contenuto senza errori SSL.
Conclusione
L’errore 20:unable to get local issuer certificate
è molto comune negli sviluppi locali che utilizzano OpenSSL: la causa primaria è la mancata indicazione di uno store di fiducia per la verifica dei certificati server. ServBay mette a disposizione un file cacert.pem
già preparato con le CA root pubbliche più comuni.
Per risolvere il problema, basta specificare il percorso corretto del file cacert.pem
di ServBay — tramite il file php.ini
, opzioni nei context SSL nei tuoi script, o parametri da riga di comando come -CAfile
per openssl
o --cacert
per curl
. Ricorda di scegliere il percorso adatto in base all’architettura del tuo Mac (Apple Silicon/Intel) e alla relativa versione OpenSSL di ServBay. Con una configurazione appropriata della CA store, garantirai che il tuo ambiente di sviluppo locale comunichi in sicurezza con i servizi esterni SSL/TLS.