When using OpenVPN with certificate-based authentication combined with LDAP, we often encounter a security gap: valid LDAP users can share certificates among themselves. While the server validates both the certificate and LDAP credentials separately, there's no inherent binding between a specific certificate and its designated LDAP user.
Here are three effective approaches to enforce certificate-user binding:
1. Using tls-verify Scripts
Create a custom script that checks if the certificate's Common Name (CN) matches the authenticated LDAP username:
#!/bin/bash # /etc/openvpn/verify-user.sh LDAP_USER="$common_name" CERT_CN=$(openssl x509 -in "$1" -noout -subject | sed 's/.*CN=//') if [ "$LDAP_USER" != "$CERT_CN" ]; then exit 1 fi exit 0
Add this to your OpenVPN server config:
tls-verify "/etc/openvpn/verify-user.sh"
2. Leveraging TLS Crypt V2
OpenVPN 2.5+ supports TLS Crypt V2 which includes client-specific keys. Combine this with certificate authentication:
tls-crypt-v2 /etc/openvpn/server/tls-crypt-v2.key
Generate per-client keys and distribute them securely.
3. Custom Certificate Fields
Extend certificates with custom fields containing the LDAP username:
openssl req -new -key bob.key -out bob.csr -subj "/CN=bob/OU=VPN/O=Company/emailAddress=bob@company.com/UID=bob"
Then modify your verification script to check this field.
For better security with LDAP:
plugin /usr/lib/openvpn/openvpn-auth-ldap.so "/etc/openvpn/auth/ldap.conf"
Sample ldap.conf snippet:
<LDAP> URL ldap://ldap.example.com BindDN cn=admin,dc=example,dc=com Password secret Timeout 15 TLSEnable no </LDAP> <Authorization> BaseDN "ou=users,dc=example,dc=com" SearchFilter "(&(uid=%u)(objectClass=posixAccount))" RequireGroup false </Authorization>
- Set appropriate key usage and extended key usage in certificates
- Implement certificate revocation (CRL or OCSP)
- Use short certificate lifetimes (30-90 days)
- Combine with multi-factor authentication where possible
When implementing OpenVPN with certificate-based authentication alongside LDAP, we face a critical security gap: certificates aren't inherently bound to specific LDAP users. This creates a vulnerability where any valid LDAP user can authenticate using any valid certificate.
The standard OpenVPN configuration with auth-user-pass-verify
only verifies that:
- The certificate is valid
- The LDAP credentials are valid
There's no built-in mechanism to verify that the certificate belongs to the LDAP user attempting to authenticate.
We'll solve this by modifying the OpenVPN server configuration to enforce certificate-to-user binding:
# In server.conf
script-security 2
auth-user-pass-verify "/etc/openvpn/scripts/verify_user_cert_binding.py" via-file
Here's the Python verification script:
#!/usr/bin/env python3
import os
import sys
from OpenSSL import crypto
# Path to user certificate mapping file
USER_CERT_MAP = "/etc/openvpn/user_cert_map.csv"
def load_credentials():
with open(sys.argv[1], 'r') as f:
return f.read().splitlines()
def extract_cn(cert_path):
with open(cert_path, 'r') as f:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
subject = cert.get_subject()
return subject.CN
def verify_binding(username, cert_cn):
with open(USER_CERT_MAP, 'r') as f:
for line in f:
stored_user, stored_cn = line.strip().split(',')
if stored_user == username and stored_cn == cert_cn:
return True
return False
if __name__ == "__main__":
username, password = load_credentials()
cert_cn = extract_cn(os.environ['tls_serial_0'])
if verify_binding(username, cert_cn):
sys.exit(0)
else:
print(f"Certificate {cert_cn} not authorized for user {username}")
sys.exit(1)
For this system to work, you need to implement a strict certificate issuance process:
1. When creating a certificate for user 'bob':
openssl req -new -newkey rsa:2048 -nodes -keyout bob.key -out bob.csr -subj "/CN=bob_cert_serial_12345"
2. Add to user_cert_map.csv:
bob,bob_cert_serial_12345
For additional security, consider implementing:
# In server.conf
# Prevent certificate reuse
explicit-exit-notify 1
auth-gen-token
tls-crypt v2.key
This combination provides certificate binding while maintaining the flexibility of LDAP authentication, creating a robust two-factor authentication system where both factors must match.