Building a Custom Public Key Infrastructure with OpenSSL

PKI Architecture Fundamentals

A self-managed Public Key Infrastructure relies on a hierarchical trust model consisting of three primary tiers:

  • Root Certificate Authority: The anchor of trust. Its private key never leaves the secure hardware store and is used exclusively to sign intermediate certificates.
  • Intermediate Certificate Authority: A delegated issuer that bridges the gap between the offline root and daily operations. It holds the actual signing power for service endpoints.
  • Leaf (End-Entity) Certificate: The final credential bound to a specific hostname or application, distributed to clients for encrypted communications.

OpenSSL Configuration Templaet

Proper credential issuance requires a centralized configuration file that defines routing paths, validation policies, and X.509 extensions. The template below establishes strict enforcement rules and modern cryptographic defaults.

# OpenSSL Policy Configuration
# File: openssl-pki.cnf

[ ca ]
default_ca = CA_intermediate

[ CA_intermediate ]
dir               = /opt/pki
database          = $dir/index.txt
new_certs_dir     = $dir/newcerts
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# Intermediate credentials
certificate       = $dir/certs/intermediate.cert.pem
private_key       = $dir/private/intermediate.key.pem
crlnumber         = $dir/crlnumber
crl               = $dir/crl/cert.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

default_md        = sha256
name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_match

[ policy_match ]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only
default_md          = sha256
x509_extensions     = v3_ca

[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province
localityName                    = City or Region
0.organizationName              = Organization Name
organizationalUnitName          = Division Name
commonName                      = Common Name
emailAddress                    = Email Address

[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = critical, CA:true
keyUsage               = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = critical, CA:true, pathlen:0
keyUsage               = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
basicConstraints       = CA:FALSE
nsCertType             = client, email
nsComment              = "OpenSSL Generated Client Cert"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
keyUsage               = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage       = clientAuth, emailProtection

[ server_cert ]
basicConstraints       = CA:FALSE
nsCertType             = server
nsComment              = "OpenSSL Generated Server Cert"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage               = critical, digitalSignature, keyEncipherment
extendedKeyUsage       = serverAuth

[ crl_ext ]
authorityKeyIdentifier = keyid:always

[ ocsp ]
basicConstraints       = CA:FALSE
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
keyUsage               = critical, digitalSignature
extendedKeyUsage       = critical, OCSPSigning

Automated Issuance Pipeline

The following workflow encapsulates the credential generation process into a reproducible sequence. By utilizing environment-driven variables, the pipeline remains adaptable across different projects while preserving cryptographic integrity.

#!/usr/bin/env bash
set -euo pipefail

# --- Runtime Parameters ---
PKI_ROOT="/opt/pki"
TARGET_DOMAIN="api.internal.corp"
KEY_SIZE=2048
HASH_ALGO="sha256"
CERT_TTL_DAYS=375

# Initialize directory structure
mkdir -p "${PKI_ROOT}/intermediate/{private,csr,certs,newcerts,crl}"
touch "${PKI_ROOT}/intermediate/index.txt"
echo "1000" > "${PKI_ROOT}/intermediate/serial"

# Step 1: Provision Intermediate Private Key
openssl genrsa \
  -aes256 \
  -out "${PKI_ROOT}/intermediate/private/${TARGET_DOMAIN}.key.pem" \
  ${KEY_SIZE}
chmod 400 "${PKI_ROOT}/intermediate/private/${TARGET_DOMAIN}.key.pem"

# Step 2: Construct Certificate Signing Request
openssl req \
  -config "${PKI_ROOT}/openssl-pki.cnf" \
  -key "${PKI_ROOT}/intermediate/private/${TARGET_DOMAIN}.key.pem" \
  -new \
  -${HASH_ALGO} \
  -out "${PKI_ROOT}/intermediate/csr/${TARGET_DOMAIN}.csr.pem" \
  -subj "/O=Internal Operations/CN=${TARGET_DOMAIN}"

# Step 3: Sign Endpoint Credential Against Intermediate CA
openssl ca \
  -config "${PKI_ROOT}/openssl-pki.cnf" \
  -extensions server_cert \
  -days ${CERT_TTL_DAYS} \
  -notext \
  -md ${HASH_ALGO} \
  -in "${PKI_ROOT}/intermediate/csr/${TARGET_DOMAIN}.csr.pem" \
  -out "${PKI_ROOT}/intermediate/certs/${TARGET_DOMAIN}.cert.pem"

# Step 4: Validate Cryptographic Chain
cat "${PKI_ROOT}/intermediate/certs/ca-chain.cert.pem" > /tmp/trust-anchor.pem
openssl verify \
  -CAfile /tmp/trust-anchor.pem \
  "${PKI_ROOT}/intermediate/certs/${TARGET_DOMAIN}.cert.pem"

# Step 5: Display Parsed Certificate Details
openssl x509 -noout -text -in "${PKI_ROOT}/intermediate/certs/${TARGET_DOMAIN}.cert.pem"

Tags: openssl pki-certificate-authority tls-handshake x509 infrastructure-security

Posted on Tue, 30 Jun 2026 17:33:41 +0000 by ldb358