Solución de problemas de OpenSSL: cómo resolver el error unable to get local issuer certificate
Al utilizar OpenSSL para establecer conexiones seguras (por ejemplo, al realizar peticiones de red en entornos PHP, ejecutar comandos openssl
o curl
, o al crear conexiones SSL/TLS en aplicaciones Node.js/Python), los desarrolladores pueden encontrarse con el aviso de error 20:unable to get local issuer certificate
. Este es un problema histórico común, relacionado con cómo OpenSSL verifica el certificado del servidor remoto.
Por motivos de seguridad, OpenSSL requiere de forma predeterminada conocer y confiar en una entidad certificadora (CA) raíz al validar la cadena de certificados. Si no encuentra estas entidades de confianza, o no se han definido rutas a ellas, OpenSSL no puede confirmar la legitimidad del certificado del servidor, lo que genera este error.
A continuación se explica detalladamente la causa de este error y cómo resolverlo dentro del entorno ServBay, incluyendo la configuración del almacén de confianza de OpenSSL en PHP, Python, Node.js y el comando curl
.
Mensaje de error 20:unable to get local issuer certificate
Descripción del problema
Cuando OpenSSL intenta validar el certificado SSL/TLS de un servidor remoto, construye una cadena de certificados desde el certificado del servidor hasta una CA raíz de confianza. Si OpenSSL no puede localizar de manera local los certificados intermedios necesarios o el certificado raíz, o no tiene configurado un almacén de confianza (CAFile o CAPath) que le permita encontrarlos, el proceso de verificación falla y devuelve el error 20:unable to get local issuer certificate
.
En resumen, esto significa que OpenSSL no sabe a qué autoridad certificadora confiar y no puede confirmar la identidad del servidor al que intenta conectar.
Versión de OpenSSL y rutas de certificados CA en ServBay
ServBay es un entorno local integrado para desarrollo web, que ya incluye el software OpenSSL junto con un paquete de certificados raíz públicos más comunes, facilitando su uso para desarrolladores. La versión de OpenSSL de ServBay depende de la arquitectura de tu dispositivo:
- ServBay para Apple Silicon (chips de la serie M): Utiliza OpenSSL versión 3.2.1.
- ServBay para Intel: Utiliza OpenSSL versión 1.1.1u.
Los archivos correspondientes de certificado CA (cacert.pem
) y el directorio de certificados (certs
) están ubicados dentro de la ruta de instalación de los paquetes de ServBay. De acuerdo con tu sistema, utiliza la ubicación adecuada:
ini
# Archivo con todos los certificados raíces confiables
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Directorio con archivos independientes de certificados (usualmente solo cacert.pem es suficiente, aunque algunas aplicaciones pueden requerir capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# Archivo con todos los certificados raíces confiables
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Directorio con archivos independientes de certificados
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
ini
# Archivo con todos los certificados raíces confiables
cafile=C:\ServBay\package\common\openssl\3.3\cacert.pem
# Directorio con archivos independientes de certificados
capath=C:\ServBay\package\common\openssl\3.3\certs
1
2
3
4
2
3
4
La clave para solucionar el error unable to get local issuer certificate
es especificar explícitamente la ruta del cafile
o capath
, indicando a OpenSSL dónde buscar los certificados de CA confiables.
Ejemplos de solución
A continuación, se explica cómo definir el almacén de confianza de CA de OpenSSL en distintos lenguajes y herramientas.
Ejemplo 1: Prueba de conexión usando el comando openssl
Si al ejecutar el comando openssl s_client
se produce el error:
bash
openssl s_client -quiet -connect gmail.com:443
1
Podrás ver una salida similar a la siguiente, que incluye el mensaje 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
# ... información adicional de la conexión ...
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Solución:
Utiliza el parámetro -CAfile
para especificar la ruta al archivo de certificados CA proporcionado por 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
Tras conectar y validar correctamente el certificado, la salida mostrará verify return:1
y ya no aparecerá la línea 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
# ... información adicional de la conexión ...
1
2
3
4
5
6
7
2
3
4
5
6
7
Ejemplo 2: Usando OpenSSL en PHP
Muchas funciones de red de PHP (como file_get_contents
sobre HTTPS, stream_socket_client
para conexiones SSL, la extensión cURL, etc.) dependen de OpenSSL. Puedes definir el almacén de confianza de CA editando el archivo php.ini
o configurando el contexto de flujo en tu código.
Método A: Editando php.ini
(recomendado)
Es la solución global más sencilla. Edita tu archivo php.ini
correspondiente (puedes acceder a la edición desde el panel de control de ServBay), busca la sección [openssl]
y añade o modifica:
ini
[openssl]
; Ruta al archivo de certificados CA de confianza
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Ruta al directorio de certificados CA (opcional, pero se recomienda definirla)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[openssl]
; Ruta al archivo de certificados CA de confianza
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Ruta al directorio de certificados CA (opcional, pero se recomienda definirla)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
Tras modificar php.ini
, reinicia el servicio PHP en ServBay (o todo ServBay) para aplicar los cambios.
Método B: Definiendo la configuración en el código (afecta solo la conexión actual)
Si no deseas ajustar la configuración global, puedes definir el contexto SSL creando un stream context y especificando el cafile
dentro del código.
php
<?php
// Ejemplo: Conexión a un servidor SMTP usando SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Elige la ruta del CA adecuada según tu versión de ServBay
// Para Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Para Intel:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // Habilita la verificación del certificado del servidor
'verify_peer_name' => true, // Verifica que el nombre del host en el certificado coincida
'allow_self_signed' => false, // No acepta certificados autofirmados, salvo explícita confianza
'cafile' => $caCertFile, // Archivo bundel CA especificado
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Opcional, ruta al directorio CA
],
];
$context = stream_context_create($contextOptions);
// Establece la conexión SSL/TLS usando el contexto creado
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // Tiempo de espera de conexión
STREAM_CLIENT_CONNECT,
$context // Opciones de contexto
);
if ($connection) {
echo "Conexión establecida con $server:$port\n";
// Ejemplo: envío del comando EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // Dominio de ejemplo relacionado con ServBay
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "No se pudo conectar con $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
Ejemplo 3: Usando OpenSSL en Python (módulo ssl)
El módulo ssl
de Python permite crear contextos SSL/TLS y definir el archivo CA de confianza.
python
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Elige la ruta del archivo CA adecuada según tu versión de ServBay
# Para Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# Para Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# Crea un contexto SSL usando el archivo CA
context = ssl.create_default_context(cafile=ca_cert_file)
# También puedes definir el directorio: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Establece una conexión socket estándar
with socket.create_connection((server, port)) as sock:
# Envuelve el socket estándar en un socket SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"Conexión SSL establecida. Protocolo negociado: {ssock.version()}")
# Ejemplo: envío del comando EHLO
ssock.sendall(b"EHLO servbay.demo\r\n") # Dominio de ejemplo relacionado con ServBay
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Error de conexión o 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
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
Ejemplo 4: Usando OpenSSL en Node.js (módulo tls)
El módulo tls
de Node.js permite conexiones TLS/SSL y puedes definir el CA de confianza mediante la propiedad ca
en las opciones de conexión. La forma más sencilla es leer el contenido del archivo cacert.pem
proporcionado por ServBay.
javascript
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Usar un sitio web estándar y confiable como ejemplo
const port = 443;
// Elige la ruta del archivo CA adecuada según tu versión de ServBay
// Para Apple Silicon:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Para Intel:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// Cargar el contenido del archivo CA
ca: fs.readFileSync(caCertFile),
// El módulo tls de Node.js valida por defecto el nombre de host (checkServerIdentity)
// Si el archivo CA está bien especificado y el certificado del servidor es válido, la verificación debe ser exitosa.
// No desactives checkServerIdentity salvo que sepas exactamente los riesgos.
// checkServerIdentity: () => { return null; } // <-- ¡No uses esto salvo que sepas lo que estás haciendo!
};
const socket = tls.connect(options, () => {
console.log('Conexión SSL establecida');
// Para una conexión HTTPS, generalmente se envía una petición HTTP, aquí es solo demostrativo
// 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('Conexión cerrada');
});
socket.on('error', (error) => {
console.error('Error:', error.message); // Mostrar el mensaje de error
});
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
Nota: En el ejemplo de Node.js se ha eliminado checkServerIdentity: () => { return null; }
. Esa opción desactivaría la verificación del nombre de host del servidor, lo que es inseguro. El error de OpenSSL unable to get local issuer certificate
está relacionado con el CA de confianza, no con la verificación de nombres. Especificando correctamente la opción ca
y contando con certificados válidos, Node.js verifica el nombre de host por defecto y no debería fallar. Si tienes errores en la verificación de nombre, normalmente es debido a problemas con el propio certificado y no con el CA especificado.
Ejemplo 5: Usando OpenSSL en el comando curl
El comando curl
emplea OpenSSL (u otras bibliotecas SSL) para sus peticiones HTTPS. Puedes indicar el archivo CA mediante el parámetro --cacert
.
bash
# Accede a un sitio HTTPS usando el archivo CA de ServBay
# Según tu versión de ServBay, usa la ruta adecuada
# Para Apple Silicon:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# Para 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
Si la ruta del CA es correcta y el certificado del sitio web es válido, curl
recuperará el contenido sin errores de verificación de certificado.
Resumen
El error 20:unable to get local issuer certificate
es muy frecuente al establecer conexiones SSL/TLS con OpenSSL. Su causa principal es que OpenSSL necesita un almacén de confianza de CA explícito para validar el certificado del servidor. ServBay pone a disposición el archivo cacert.pem
con los certificados raíz públicos más usuales.
La forma de solucionar el problema es especificar la ruta al archivo cacert.pem
de ServBay en tu entorno de desarrollo (ya sea mediante el archivo php.ini
, opciones de contexto SSL en código, parámetros de línea de comandos como -CAfile
para openssl
o --cacert
para curl
). Elige el camino correcto según tu tipo de chip en macOS (Apple Silicon o Intel) y versión de OpenSSL en ServBay. Una correcta configuración del almacén de confianza CA asegurará que tu entorno local pueda comunicarse de manera segura con servicios externos SSL/TLS.