How to Fix “SSL verify error:num=20: unable to get local issuer certificate” in LDAPS Connections


2 views

The error verify error:num=20:unable to get local issuer certificate typically occurs when OpenSSL cannot find the root certificate that signed the server's SSL certificate. This is common when connecting to LDAPS (LDAP over SSL) servers, especially in enterprise environments with internal Certificate Authorities (CAs).

When you tried:

openssl s_client -CAfile mycert.pem -connect the.server.edu:3269

The issue is that you likely saved the server's certificate (not the CA certificate) in mycert.pem. For verification to work, you need the CA's root certificate that actually signed the server's certificate.

Here's how to properly handle this:

# First, get the server's certificate chain
openssl s_client -showcerts -connect the.server.edu:3269 < /dev/null > chain.pem

# Then extract the CA certificate (usually the last cert in the chain)
# You might need to manually identify which one is the CA cert
openssl x509 -in chain.pem -text | less

For testing purposes only, you can disable certificate verification:

openssl s_client -connect the.server.edu:3269 -verify 0

Warning: This bypasses security checks and should never be used in production.

For production systems, you should:

  1. Obtain the proper CA certificate from your IT department
  2. Add it to your system's trusted store or specify it explicitly:
openssl s_client -connect the.server.edu:3269 -CAfile /path/to/company-ca.crt

To better understand the certificate chain:

openssl s_client -showcerts -connect the.server.edu:3269 < /dev/null

This will show you all certificates presented by the server, helping you identify which CA certificate is missing.

For scripting purposes, you might want to handle this programmatically. Here's a Python example using the ssl module:

import ssl
import socket

context = ssl.create_default_context()
context.load_verify_locations('/path/to/ca-certificates.crt')

with socket.create_connection(('the.server.edu', 3269)) as sock:
    with context.wrap_socket(sock, server_hostname='the.server.edu') as ssock:
        print(ssock.version())

When establishing an LDAPS connection to Active Directory, the error verify error:num=20:unable to get local issuer certificate typically indicates that OpenSSL cannot find the root certificate needed to verify the server's certificate chain. This is fundamentally a trust chain validation problem.

The common misconception is that providing the server's certificate directly via -CAfile should suffice:

openssl s_client -CAfile mycert.pem -connect the.server.edu:3269

This fails because:

  • The server might be presenting intermediate certificates
  • The certificate might be self-signed
  • The complete chain might not be properly configured on the server

First, capture the full certificate chain:

openssl s_client -showcerts -connect the.server.edu:3269 /dev/null | \
awk '/BEGIN CERT/,/END CERT/{ if(/BEGIN CERT/){a++}; out="cert"a".pem"; print >out}'

Then verify each certificate level:

for cert in *.pem; do 
  openssl x509 -in $cert -noout -issuer -subject
done

Option 1: Trust the specific certificate (bypass verification)

openssl s_client -connect the.server.edu:3269 -verify_return_error -verify 0

Option 2: Build complete CA chain file

cat rootCA.pem intermediateCA.pem > fullchain.pem
openssl s_client -CAfile fullchain.pem -connect the.server.edu:3269

Option 3: Add certificate to system trust store

sudo cp mycert.pem /usr/local/share/ca-certificates/
sudo update-ca-certificates

For Active Directory environments:

  • Check if Schannel is enforcing specific protocols
  • Verify the certificate has proper SAN entries for LDAP service
  • Consider using ldapsearch with explicit trust options:
ldapsearch -H ldaps://the.server.edu:3269 -x -ZZ \
  -o tls_certfile=/path/to/cacert.pem

Here's a robust verification script:

#!/bin/bash
SERVER="the.server.edu"
PORT=3269

# Get certificate chain
openssl s_client -connect ${SERVER}:${PORT} -showcerts /dev/null > chain.pem

# Extract individual certificates
csplit -f cert- chain.pem '/-----BEGIN CERTIFICATE-----/' '{*}'

# Verify chain
for cert in cert-*; do
  echo "Verifying $cert:"
  openssl verify -CAfile <(cat cert-* | grep -v "$(cat $cert)") $cert
done

For Windows environments using Schannel:

# PowerShell equivalent for testing
Test-NetConnection -ComputerName the.server.edu -Port 3269
$request = [System.Net.WebRequest]::Create("https://the.server.edu:3269")
$request.GetResponse()