Overview
Certificates provide a mechanism for verifying the identity of entities in distributed systems. A digital certificate binds a public key to an identity through a cryptographic signature from a trusted authority. This binding allows systems to verify that a public key belongs to the claimed entity without prior direct knowledge.
PKI encompasses the complete infrastructure for creating, distributing, managing, and revoking certificates. The system relies on a hierarchical trust model where Certificate Authorities (CAs) vouch for the identity of certificate holders. When a client receives a certificate, it verifies the signature chain up to a trusted root CA to establish trust.
The most common certificate format is X.509, defined in RFC 5280. X.509 certificates contain the subject's public key, identity information, issuer details, validity period, and a signature from the issuing CA. These certificates form the foundation of TLS/SSL, code signing, email encryption, and numerous authentication systems.
require 'openssl'
# Load and inspect a certificate
cert_pem = File.read('example.crt')
cert = OpenSSL::X509::Certificate.new(cert_pem)
puts "Subject: #{cert.subject}"
puts "Issuer: #{cert.issuer}"
puts "Valid from: #{cert.not_before}"
puts "Valid until: #{cert.not_after}"
# => Subject: /CN=example.com/O=Example Corp
# => Issuer: /CN=Example CA/O=Example Trust Services
# => Valid from: 2024-01-01 00:00:00 UTC
# => Valid until: 2025-01-01 00:00:00 UTC
PKI implementations appear in web browsers trusting HTTPS certificates, operating systems validating signed software, email clients encrypting messages with S/MIME, and VPN systems authenticating connections. The infrastructure handles billions of certificate validations daily across the internet.
Key Principles
Trust Anchors and Certificate Chains
Trust in PKI derives from pre-installed root certificates that serve as trust anchors. Operating systems and browsers ship with a set of trusted root CA certificates. When verifying a certificate, the system builds a chain from the presented certificate up through intermediate CAs to a trusted root. Each certificate in the chain must be valid and properly signed by the certificate above it.
The chain verification process checks multiple conditions: signature validity, validity period, usage constraints, and revocation status. A chain breaks if any certificate is expired, has an invalid signature, or has been revoked. The chain must terminate at a trusted root certificate for verification to succeed.
Public Key Cryptography Foundation
Certificates contain a public key corresponding to a private key held by the certificate subject. The subject uses the private key to prove ownership of the certificate by signing data or decrypting messages encrypted with the public key. This asymmetric cryptography ensures that possessing the certificate alone does not grant the ability to impersonate the subject.
The certificate issuer creates a signature by hashing the certificate contents and encrypting the hash with their private key. Verifiers decrypt this signature using the issuer's public key (found in the issuer's certificate) and compare it to a hash they compute. Match confirms the certificate has not been tampered with and was issued by the claimed authority.
Certificate Lifecycle
Certificates progress through distinct lifecycle phases. Generation creates a key pair and Certificate Signing Request (CSR). The CSR contains the public key and identity information. The CA validates the identity claims, signs the CSR to create a certificate, and publishes the certificate.
During active use, certificates authenticate connections and sign data. The certificate remains valid until expiration or revocation. Revocation occurs when a private key is compromised, identity information changes, or the certificate is no longer needed. CAs publish revocation information through Certificate Revocation Lists (CRLs) or Online Certificate Status Protocol (OCSP) responses.
Renewal occurs before expiration for continued service. Renewal may reuse the same key pair or generate new keys. Many organizations automate renewal through protocols like ACME (Automated Certificate Management Environment).
X.509 Certificate Structure
X.509 certificates use ASN.1 (Abstract Syntax Notation One) encoding, typically serialized as DER (Distinguished Encoding Rules) and often transmitted in Base64-encoded PEM format. The certificate structure includes version, serial number, signature algorithm, issuer, validity period, subject, subject public key info, and extensions.
Extensions provide additional functionality beyond the basic certificate. Key Usage extensions specify allowed cryptographic operations (signing, encryption, certificate signing). Extended Key Usage defines high-level purposes (server authentication, client authentication, code signing). Subject Alternative Names (SAN) list additional identities like domain names or IP addresses.
Critical extensions must be understood and processed by all verifiers. Non-critical extensions can be ignored if not recognized. This extensibility allows PKI to evolve while maintaining backward compatibility.
Certificate Authorities and Trust Models
CAs act as trusted third parties that verify identities before issuing certificates. The CA model creates a hierarchical trust structure. Root CAs sit at the top, self-signed and distributed through operating system updates or browser installations. Intermediate CAs receive certificates from root CAs and issue end-entity certificates.
This hierarchy provides operational security and business flexibility. Root CA private keys remain offline in Hardware Security Modules (HSMs), used only to sign intermediate CA certificates. Intermediate CAs handle day-to-day certificate issuance. If an intermediate CA is compromised, only its certificate needs revocation, not the entire root.
Domain Validation (DV) certificates verify only domain ownership through DNS records or HTTP responses. Organization Validation (OV) certificates require verification of organizational identity through business registries. Extended Validation (EV) certificates require extensive verification including legal and physical existence. The validation level affects the trust indicators shown in browsers and the use cases where the certificate is appropriate.
Ruby Implementation
Ruby provides certificate functionality through the OpenSSL module, which wraps the OpenSSL library. The module supports certificate creation, parsing, verification, and management operations.
Certificate Loading and Parsing
require 'openssl'
# Load from PEM format
pem_cert = File.read('server.crt')
cert = OpenSSL::X509::Certificate.new(pem_cert)
# Load from DER format
der_cert = File.read('server.der')
cert = OpenSSL::X509::Certificate.new(der_cert)
# Parse certificate details
subject = cert.subject.to_s
issuer = cert.issuer.to_s
serial = cert.serial.to_i
not_before = cert.not_before
not_after = cert.not_after
# Extract public key
public_key = cert.public_key
key_type = public_key.class.name
# => "OpenSSL::PKey::RSA" or "OpenSSL::PKey::EC"
Certificate objects expose attributes as Ruby methods. Subject and issuer return OpenSSL::X509::Name objects that represent distinguished names. Serial numbers return as OpenSSL::BN (BigNum) objects. Converting to Ruby integers or strings depends on the use case.
Certificate Verification
# Verify certificate signature
ca_cert = OpenSSL::X509::Certificate.new(File.read('ca.crt'))
if cert.verify(ca_cert.public_key)
puts "Certificate signature valid"
else
puts "Certificate signature invalid"
end
# Check validity period
now = Time.now
if now >= cert.not_before && now <= cert.not_after
puts "Certificate currently valid"
else
puts "Certificate expired or not yet valid"
end
# Build and verify certificate chain
store = OpenSSL::X509::Store.new
store.add_cert(ca_cert)
store.add_cert(root_cert)
# Set verification parameters
store.purpose = OpenSSL::X509::PURPOSE_SSL_SERVER
store.verify_callback = proc do |ok, ctx|
unless ok
puts "Verification failed: #{ctx.error_string}"
end
ok
end
if store.verify(cert)
puts "Certificate chain valid"
else
puts "Certificate chain verification failed"
end
OpenSSL::X509::Store manages trust anchors and performs chain verification. The store accepts multiple certificates and builds chains automatically. Setting the purpose parameter constrains verification to specific use cases, checking key usage extensions match the intended purpose.
Certificate Creation
# Generate RSA key pair
key = OpenSSL::PKey::RSA.new(2048)
# Create certificate
cert = OpenSSL::X509::Certificate.new
cert.version = 2 # X.509 v3
cert.serial = 1
cert.subject = OpenSSL::X509::Name.parse("/CN=example.com/O=Example Corp")
cert.issuer = cert.subject # Self-signed
cert.public_key = key.public_key
cert.not_before = Time.now
cert.not_after = Time.now + (365 * 24 * 60 * 60) # 1 year
# Add extensions
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = cert
cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
cert.add_extension(ef.create_extension("keyUsage", "keyCertSign,cRLSign", true))
cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
# Sign certificate
cert.sign(key, OpenSSL::Digest::SHA256.new)
# Export to PEM
File.write('self_signed.crt', cert.to_pem)
File.write('private_key.key', key.to_pem)
ExtensionFactory manages extension creation with proper encoding. Extensions require careful configuration of critical flags and value formats. The basicConstraints extension with CA:TRUE allows the certificate to sign other certificates. KeyUsage restricts cryptographic operations the key can perform.
Certificate Signing Requests
# Generate key
key = OpenSSL::PKey::RSA.new(2048)
# Create CSR
csr = OpenSSL::X509::Request.new
csr.version = 0
csr.subject = OpenSSL::X509::Name.parse("/CN=server.example.com/O=Example Corp")
csr.public_key = key.public_key
# Add extensions to CSR
extensions = []
extensions << OpenSSL::X509::Extension.new("subjectAltName",
"DNS:server.example.com,DNS:www.example.com")
extensions << OpenSSL::X509::Extension.new("keyUsage",
"digitalSignature,keyEncipherment")
# Create extension request attribute
ext_req = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(extensions)])
csr.add_attribute(OpenSSL::X509::Attribute.new("extReq", ext_req))
# Sign CSR
csr.sign(key, OpenSSL::Digest::SHA256.new)
# Export CSR
File.write('server.csr', csr.to_pem)
# CA signs the CSR
ca_key = OpenSSL::PKey::RSA.new(File.read('ca_key.pem'))
ca_cert = OpenSSL::X509::Certificate.new(File.read('ca.crt'))
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 2
cert.subject = csr.subject
cert.issuer = ca_cert.subject
cert.public_key = csr.public_key
cert.not_before = Time.now
cert.not_after = Time.now + (365 * 24 * 60 * 60)
# Copy extensions from CSR
csr.attributes.each do |attr|
if attr.oid == "extReq"
extensions = attr.value.value.first.value
extensions.each do |ext|
cert.add_extension(ext)
end
end
end
cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
CSR handling requires extracting extensions from the CSR attributes and transferring them to the signed certificate. The CA validates identity claims before signing. Extensions requested in the CSR may be modified or rejected by the CA based on policy.
SSL/TLS Context Configuration
require 'socket'
require 'openssl'
# Server context
server_ctx = OpenSSL::SSL::SSLContext.new
server_ctx.cert = OpenSSL::X509::Certificate.new(File.read('server.crt'))
server_ctx.key = OpenSSL::PKey::RSA.new(File.read('server.key'))
server_ctx.ca_file = 'ca_bundle.crt'
server_ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
# Client context
client_ctx = OpenSSL::SSL::SSLContext.new
client_ctx.ca_file = 'ca_bundle.crt'
client_ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
client_ctx.verify_hostname = true
# Custom verification
client_ctx.verify_callback = proc do |preverify_ok, store_ctx|
if preverify_ok
cert = store_ctx.current_cert
if store_ctx.error_depth.zero?
# Verify specific properties of end-entity cert
unless cert.subject.to_s.include?("CN=expected.example.com")
store_ctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
false
else
true
end
else
true
end
else
puts "Cert verification failed: #{store_ctx.error_string}"
false
end
end
SSLContext objects configure certificate verification behavior for SSL/TLS connections. The verify_mode controls whether peer certificates are checked. VERIFY_PEER enables verification, VERIFY_FAIL_IF_NO_PEER_CERT requires the peer to present a certificate. Verify callbacks allow custom validation logic beyond standard chain verification.
Security Implications
Private Key Protection
Private keys must remain confidential. Compromise of a private key allows an attacker to impersonate the certificate holder, decrypt data, or forge signatures. Keys should never be transmitted over insecure channels, stored in version control, or logged.
Hardware Security Modules (HSMs) provide the strongest private key protection. HSMs store keys in tamper-resistant hardware that performs cryptographic operations without exposing key material. For less critical applications, encrypted key storage with strong passphrases provides reasonable protection.
# Generate encrypted private key
key = OpenSSL::PKey::RSA.new(2048)
cipher = OpenSSL::Cipher.new('AES-256-CBC')
encrypted_key = key.to_pem(cipher, 'strong_passphrase')
File.write('encrypted.key', encrypted_key)
# Load encrypted key
encrypted_pem = File.read('encrypted.key')
key = OpenSSL::PKey::RSA.new(encrypted_pem, 'strong_passphrase')
File permissions should restrict key access to the application user. Keys should not be world-readable. Configuration management systems handling keys require secure storage mechanisms like encrypted variables or secrets management services.
Certificate Validation Requirements
Applications must perform complete certificate validation. Partial validation creates security vulnerabilities. Required checks include signature verification, chain building to a trusted root, validity period verification, and revocation checking.
Hostname verification ensures the certificate matches the connected host. For HTTPS, the certificate Common Name (CN) or Subject Alternative Name must match the requested domain. Wildcard certificates match subdomains according to specific rules defined in RFC 6125.
require 'net/http'
require 'openssl'
uri = URI('https://example.com')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
# Proper verification (default in modern Ruby)
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.cert_store = OpenSSL::X509::Store.new
http.cert_store.set_default_paths
# WRONG: Disabling verification creates vulnerabilities
# http.verify_mode = OpenSSL::SSL::VERIFY_NONE
response = http.request(Net::HTTP::Get.new(uri))
Disabling certificate verification exposes applications to man-in-the-middle attacks. Attackers can intercept connections and present fraudulent certificates. Never disable verification in production code, even for testing or debugging.
Revocation Checking
Certificate revocation occurs when a certificate must be invalidated before expiration. Common reasons include private key compromise, change in information, or cessation of operations. Applications should check revocation status to avoid trusting compromised certificates.
CRL (Certificate Revocation List) checking downloads a list of revoked certificate serial numbers from the CA. CRLs can grow large and may be cached for extended periods, creating a window where revoked certificates remain trusted.
OCSP (Online Certificate Status Protocol) provides real-time revocation status for individual certificates. OCSP requests contain the certificate serial number and receive a signed response indicating good, revoked, or unknown status. OCSP adds latency to certificate validation but provides current information.
# Enable OCSP checking
store = OpenSSL::X509::Store.new
store.add_cert(ca_cert)
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
# OCSP request
cert_id = OpenSSL::OCSP::CertificateId.new(cert, issuer_cert)
request = OpenSSL::OCSP::Request.new
request.add_certid(cert_id)
# Send OCSP request
ocsp_uri = URI(cert.extensions.find { |e| e.oid == 'authorityInfoAccess' }.value)
http = Net::HTTP.new(ocsp_uri.host, ocsp_uri.port)
http.use_ssl = (ocsp_uri.scheme == 'https')
response = http.post(ocsp_uri.path, request.to_der,
'Content-Type' => 'application/ocsp-request')
ocsp_response = OpenSSL::OCSP::Response.new(response.body)
if ocsp_response.basic.status.first.first == OpenSSL::OCSP::V_CERTSTATUS_GOOD
puts "Certificate not revoked"
end
OCSP stapling improves performance by having the server provide a signed OCSP response during TLS handshake. Clients verify the stapled response without making separate OCSP requests. This reduces latency and privacy concerns from OCSP requests revealing browsing patterns.
Certificate Pinning
Certificate pinning binds an application to specific certificates or public keys, preventing attacks involving fraudulently issued certificates from trusted CAs. Mobile applications and high-security services frequently implement pinning.
# Pin specific certificate
expected_cert_der = File.read('pinned_cert.der')
expected_digest = OpenSSL::Digest::SHA256.digest(expected_cert_der)
# Verification callback checking pin
verify_callback = proc do |preverify_ok, store_ctx|
if preverify_ok && store_ctx.error_depth.zero?
cert = store_ctx.current_cert
cert_digest = OpenSSL::Digest::SHA256.digest(cert.to_der)
if cert_digest == expected_digest
true
else
puts "Certificate pin mismatch"
store_ctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
false
end
else
preverify_ok
end
end
Public key pinning pins the public key instead of the entire certificate, allowing certificate renewal with the same key. Pinning introduces operational risk since pinned certificates cannot be quickly replaced. Applications should pin backup keys to allow rotation if the primary key is compromised.
Practical Examples
HTTPS Client with Full Verification
require 'net/http'
require 'openssl'
class SecureHttpClient
def initialize(ca_cert_path)
@store = OpenSSL::X509::Store.new
@store.add_file(ca_cert_path)
@store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
end
def get(url)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.cert_store = @store
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_hostname = true
# Set timeout values
http.open_timeout = 10
http.read_timeout = 30
request = Net::HTTP::Get.new(uri)
response = http.request(request)
# Verify certificate details after connection
peer_cert = http.peer_cert
puts "Connected to: #{peer_cert.subject}"
puts "Issuer: #{peer_cert.issuer}"
puts "Valid until: #{peer_cert.not_after}"
response
rescue OpenSSL::SSL::SSLError => e
puts "SSL Error: #{e.message}"
raise
end
end
# Usage
client = SecureHttpClient.new('/etc/ssl/certs/ca-bundle.crt')
response = client.get('https://example.com')
# => Connected to: /CN=example.com
# => Issuer: /CN=DigiCert TLS RSA SHA256 2020 CA1
# => Valid until: 2025-03-15 23:59:59 UTC
This implementation performs complete certificate verification including chain validation, hostname checking, and timeout configuration. The peer certificate becomes accessible after connection establishment for logging or additional verification.
Certificate Management Service
class CertificateManager
attr_reader :store
def initialize
@store = OpenSSL::X509::Store.new
@certificates = {}
end
def add_trusted_root(cert_path)
cert = load_certificate(cert_path)
@store.add_cert(cert)
@certificates[cert.subject.to_s] = {
cert: cert,
type: :root,
added: Time.now
}
end
def verify_certificate_chain(cert_path)
cert = load_certificate(cert_path)
# Verify chain
unless @store.verify(cert)
return {
valid: false,
error: "Chain verification failed",
cert: cert
}
end
# Check validity period
now = Time.now
if now < cert.not_before
return {
valid: false,
error: "Certificate not yet valid",
cert: cert,
not_before: cert.not_before
}
end
if now > cert.not_after
return {
valid: false,
error: "Certificate expired",
cert: cert,
not_after: cert.not_after
}
end
# Extract chain
chain = build_chain(cert)
{
valid: true,
cert: cert,
chain: chain,
chain_length: chain.length
}
end
def certificate_info(cert_path)
cert = load_certificate(cert_path)
{
subject: cert.subject.to_s,
issuer: cert.issuer.to_s,
serial: cert.serial.to_i,
not_before: cert.not_before,
not_after: cert.not_after,
signature_algorithm: cert.signature_algorithm,
public_key_type: cert.public_key.class.name,
extensions: extract_extensions(cert)
}
end
private
def load_certificate(path)
OpenSSL::X509::Certificate.new(File.read(path))
rescue => e
raise "Failed to load certificate: #{e.message}"
end
def build_chain(cert)
chain = [cert]
current = cert
while current.issuer.to_s != current.subject.to_s
issuer_cert = find_issuer(current)
break unless issuer_cert
chain << issuer_cert
current = issuer_cert
end
chain
end
def find_issuer(cert)
@certificates.values
.map { |entry| entry[:cert] }
.find { |c| c.subject.to_s == cert.issuer.to_s }
end
def extract_extensions(cert)
cert.extensions.map do |ext|
{
oid: ext.oid,
value: ext.value,
critical: ext.critical?
}
end
end
end
# Usage
manager = CertificateManager.new
manager.add_trusted_root('root_ca.crt')
manager.add_trusted_root('intermediate_ca.crt')
result = manager.verify_certificate_chain('server.crt')
if result[:valid]
puts "Certificate valid"
puts "Chain length: #{result[:chain_length]}"
else
puts "Verification failed: #{result[:error]}"
end
info = manager.certificate_info('server.crt')
puts "Subject: #{info[:subject]}"
puts "Expires: #{info[:not_after]}"
This service manages certificate validation with support for multiple trust anchors and detailed error reporting. The chain building functionality reconstructs certificate chains for analysis.
Automated Certificate Renewal
require 'acme-client'
class CertificateRenewer
RENEW_THRESHOLD = 30 * 24 * 60 * 60 # 30 days
def initialize(account_key_path, directory_url)
@account_key = OpenSSL::PKey::RSA.new(File.read(account_key_path))
@client = Acme::Client.new(
private_key: @account_key,
directory: directory_url
)
end
def needs_renewal?(cert_path)
cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
remaining = cert.not_after - Time.now
remaining < RENEW_THRESHOLD
end
def renew_certificate(domain, challenge_dir)
# Create order
order = @client.new_order(identifiers: [domain])
# Process authorization challenges
authorization = order.authorizations.first
challenge = authorization.http
# Write challenge file
challenge_path = File.join(challenge_dir, challenge.filename)
File.write(challenge_path, challenge.file_content)
# Request validation
challenge.request_validation
# Wait for validation
while challenge.status == 'pending'
sleep(2)
challenge.reload
end
unless challenge.status == 'valid'
raise "Challenge validation failed: #{challenge.status}"
end
# Generate CSR
private_key = OpenSSL::PKey::RSA.new(2048)
csr = Acme::Client::CertificateRequest.new(
private_key: private_key,
subject: { common_name: domain }
)
# Finalize order
order.finalize(csr: csr)
# Wait for certificate
while order.status == 'processing'
sleep(2)
order.reload
end
unless order.status == 'valid'
raise "Order processing failed: #{order.status}"
end
# Retrieve certificate
certificate = order.certificate
{
certificate: certificate,
private_key: private_key.to_pem,
chain: order.certificate(force_chain: true)
}
ensure
# Cleanup challenge file
File.delete(challenge_path) if challenge_path && File.exist?(challenge_path)
end
end
# Usage
renewer = CertificateRenewer.new(
'account.key',
'https://acme-v02.api.letsencrypt.org/directory'
)
if renewer.needs_renewal?('current.crt')
result = renewer.renew_certificate('example.com', '/var/www/html/.well-known/acme-challenge')
File.write('renewed.crt', result[:certificate])
File.write('renewed.key', result[:private_key])
puts "Certificate renewed successfully"
end
Automated renewal prevents service disruptions from certificate expiration. The ACME protocol standardizes certificate issuance, enabling automated domain validation and certificate retrieval.
Common Pitfalls
Incomplete Certificate Verification
Many applications verify certificate signatures but skip other critical checks. Chain validation, expiration checking, and hostname verification must all occur for secure certificate validation.
# WRONG: Only checking signature
def insecure_verify(cert, ca_cert)
cert.verify(ca_cert.public_key)
end
# CORRECT: Complete verification
def secure_verify(cert, ca_cert, expected_hostname)
# Check signature
return false unless cert.verify(ca_cert.public_key)
# Check validity period
now = Time.now
return false if now < cert.not_before || now > cert.not_after
# Check hostname
san_extension = cert.extensions.find { |e| e.oid == 'subjectAltName' }
if san_extension
san_values = san_extension.value.split(',').map(&:strip)
hostnames = san_values.select { |v| v.start_with?('DNS:') }
.map { |v| v.sub('DNS:', '') }
return false unless hostnames.include?(expected_hostname)
else
cn = cert.subject.to_a.find { |attr| attr[0] == 'CN' }
return false unless cn && cn[1] == expected_hostname
end
true
end
Signature verification alone does not establish trust. Expired certificates or mismatched hostnames indicate potential attacks or misconfigurations.
Certificate Format Confusion
Certificates exist in multiple formats: PEM (Base64-encoded with headers), DER (binary), PKCS#12 (password-protected container), and others. Loading the wrong format causes cryptic errors.
# Detect and load appropriate format
def load_flexible(cert_data)
# Try PEM first
begin
return OpenSSL::X509::Certificate.new(cert_data)
rescue OpenSSL::X509::CertificateError
end
# Try DER
begin
return OpenSSL::X509::Certificate.new(cert_data)
rescue OpenSSL::X509::CertificateError
end
# Try PKCS#12
begin
p12 = OpenSSL::PKCS12.new(cert_data)
return p12.certificate
rescue OpenSSL::PKCS12::PKCS12Error
end
raise "Unable to parse certificate in any known format"
end
PEM format begins with -----BEGIN CERTIFICATE-----. DER format starts with binary data (typically 0x30 0x82). PKCS#12 format is binary and usually password-protected. Always verify the certificate format before attempting to load.
Key Size and Algorithm Weaknesses
Older certificates may use weak cryptographic algorithms or insufficient key sizes. RSA keys below 2048 bits or SHA-1 signatures are no longer considered secure.
def check_certificate_strength(cert)
issues = []
# Check signature algorithm
if cert.signature_algorithm.include?('sha1')
issues << "Uses weak SHA-1 signature algorithm"
end
# Check key size
if cert.public_key.is_a?(OpenSSL::PKey::RSA)
key_size = cert.public_key.n.num_bits
if key_size < 2048
issues << "RSA key size #{key_size} bits too small (minimum 2048)"
end
elsif cert.public_key.is_a?(OpenSSL::PKey::EC)
curve = cert.public_key.group.curve_name
unless ['prime256v1', 'secp384r1', 'secp521r1'].include?(curve)
issues << "ECC curve #{curve} not recommended"
end
end
# Check validity period
lifetime = cert.not_after - cert.not_before
if lifetime > (397 * 24 * 60 * 60) # More than 397 days
issues << "Certificate lifetime exceeds browser maximum (397 days)"
end
issues
end
cert = OpenSSL::X509::Certificate.new(File.read('cert.crt'))
issues = check_certificate_strength(cert)
if issues.any?
puts "Certificate security issues:"
issues.each { |issue| puts " - #{issue}" }
end
Browsers and operating systems actively remove support for weak cryptography. Certificates using deprecated algorithms face rejection even if not expired.
Trust Store Management
Applications using custom trust stores must keep them updated. Expired root certificates or missing intermediate certificates cause validation failures.
# WRONG: Hardcoded old root certificate
def bad_trust_store
store = OpenSSL::X509::Store.new
store.add_cert(OpenSSL::X509::Certificate.new(HARDCODED_ROOT_2015))
store
end
# CORRECT: Use system trust store and allow updates
def good_trust_store
store = OpenSSL::X509::Store.new
store.set_default_paths # Uses system certificates
# Optionally add specific certificates
if File.exist?('/etc/ssl/certs/custom_root.crt')
store.add_file('/etc/ssl/certs/custom_root.crt')
end
store
end
System trust stores receive updates through operating system patches. Custom trust stores require manual maintenance. Always prefer system trust stores unless specific requirements dictate otherwise.
Extension Handling Errors
Certificate extensions control usage and constraints. Ignoring extensions or misinterpreting critical flags creates security vulnerabilities.
def validate_key_usage(cert, required_usage)
key_usage_ext = cert.extensions.find { |e| e.oid == 'keyUsage' }
unless key_usage_ext
return false if required_usage # No usage extension when required
return true # No restrictions if extension absent
end
# Check critical flag
if key_usage_ext.critical?
# Critical extension must be understood and respected
usage_values = key_usage_ext.value.split(',').map(&:strip)
required_usage.all? { |req| usage_values.include?(req) }
else
# Non-critical extension is advisory
true
end
end
cert = OpenSSL::X509::Certificate.new(File.read('cert.crt'))
# Verify certificate can be used for TLS server auth
if validate_key_usage(cert, ['digitalSignature', 'keyEncipherment'])
puts "Certificate suitable for TLS server"
else
puts "Certificate lacks required key usage"
end
Critical extensions must be processed. Unknown critical extensions require rejection of the certificate. Non-critical extensions provide additional information but do not prevent certificate use if unrecognized.
Tools & Ecosystem
OpenSSL Command Line
The OpenSSL command-line tool provides certificate operations outside application code. Administrators use OpenSSL for certificate inspection, conversion, and validation.
Examine certificate details:
openssl x509 -in certificate.crt -text -noout
openssl x509 -in certificate.crt -subject -issuer -dates -noout
Convert between formats:
openssl x509 -in cert.pem -outform der -out cert.der
openssl x509 -in cert.der -inform der -outform pem -out cert.pem
openssl pkcs12 -export -in cert.crt -inkey private.key -out cert.p12
Verify certificates:
openssl verify -CAfile ca_bundle.crt certificate.crt
openssl s_client -connect example.com:443 -showcerts
Generate keys and CSRs:
openssl genrsa -out private.key 2048
openssl req -new -key private.key -out request.csr
openssl req -x509 -new -key private.key -days 365 -out certificate.crt
Ruby Certificate Gems
The openssl gem ships with Ruby and wraps the OpenSSL library. Additional gems provide higher-level abstractions and specific functionality.
The certificate_authority gem simplifies CA operations:
require 'certificate_authority'
root = CertificateAuthority::Certificate.new
root.subject.common_name = "Root CA"
root.serial_number.number = 1
root.signing_entity = true
root.key_material.generate_key(2048)
root.sign!(root.key_material)
server = CertificateAuthority::Certificate.new
server.subject.common_name = "server.example.com"
server.serial_number.number = 2
server.parent = root
server.key_material.generate_key(2048)
server.sign!(root.key_material)
The acme-client gem implements the ACME protocol for automated certificate issuance from services like Let's Encrypt.
Certificate Monitoring and Management
Certificate expiration causes service outages. Monitoring systems track certificate validity and alert before expiration.
require 'net/http'
require 'openssl'
class CertificateMonitor
ALERT_THRESHOLD = 30 * 24 * 60 * 60 # 30 days
def check_host(hostname, port = 443)
tcp_socket = TCPSocket.new(hostname, port)
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
ssl_socket.connect
cert = ssl_socket.peer_cert
remaining = cert.not_after - Time.now
{
hostname: hostname,
subject: cert.subject.to_s,
issuer: cert.issuer.to_s,
not_after: cert.not_after,
days_remaining: (remaining / (24 * 60 * 60)).to_i,
needs_renewal: remaining < ALERT_THRESHOLD
}
ensure
ssl_socket.close if ssl_socket
tcp_socket.close if tcp_socket
end
def monitor_certificates(hosts)
hosts.map do |host|
begin
check_host(host)
rescue => e
{
hostname: host,
error: e.message
}
end
end
end
end
monitor = CertificateMonitor.new
results = monitor.monitor_certificates(['example.com', 'www.example.com'])
results.each do |result|
if result[:error]
puts "#{result[:hostname]}: ERROR - #{result[:error]}"
elsif result[:needs_renewal]
puts "#{result[:hostname]}: WARNING - expires in #{result[:days_remaining]} days"
else
puts "#{result[:hostname]}: OK - #{result[:days_remaining]} days remaining"
end
end
Automated monitoring prevents expiration-related outages. Many organizations use dedicated certificate management platforms that track certificates across all services and automate renewal.
Hardware Security Modules
HSMs protect private keys in tamper-resistant hardware. PKI implementations for sensitive applications store CA private keys in HSMs to prevent key compromise.
Ruby applications interact with HSMs through the PKCS#11 interface:
require 'pkcs11'
pkcs11 = PKCS11.open('/usr/lib/libsofthsm2.so')
slot = pkcs11.active_slots.first
session = slot.open
# Login to HSM
session.login(:USER, 'pin')
# Find private key
key = session.find_objects(CLASS: :PRIVATE_KEY, LABEL: 'ca_key').first
# Sign data
data = "data to sign"
signature = session.sign(:SHA256_RSA_PKCS, key, data)
session.logout
session.close
HSM integration adds complexity and cost but provides strong security guarantees for critical keys. Organizations running their own CAs typically use HSMs for root and intermediate CA keys.
Reference
Certificate Components
| Component | Description | Format |
|---|---|---|
| Version | X.509 version number | Integer (0 for v1, 2 for v3) |
| Serial Number | Unique identifier per issuer | Positive integer |
| Signature Algorithm | Algorithm used to sign certificate | OID (e.g., sha256WithRSAEncryption) |
| Issuer | Entity that issued certificate | Distinguished Name |
| Validity | Not Before and Not After dates | UTC time |
| Subject | Entity identified by certificate | Distinguished Name |
| Subject Public Key Info | Public key and algorithm | Algorithm OID + key data |
| Extensions | Additional constraints and info | OID + value + critical flag |
Common X.509 Extensions
| Extension | Purpose | Critical | Common Values |
|---|---|---|---|
| basicConstraints | CA certificate indicator | Yes | CA:TRUE, CA:FALSE, pathlen:0 |
| keyUsage | Allowed key operations | Yes | digitalSignature, keyEncipherment, keyCertSign |
| extendedKeyUsage | High-level purposes | No | serverAuth, clientAuth, codeSigning |
| subjectAltName | Alternative identities | No | DNS, IP, email addresses |
| authorityKeyIdentifier | Issuer key identifier | No | keyid hash |
| subjectKeyIdentifier | Subject key identifier | No | keyid hash |
| crlDistributionPoints | CRL locations | No | URI list |
| authorityInfoAccess | CA information access | No | OCSP, caIssuers URIs |
OpenSSL::X509 Key Classes
| Class | Purpose | Key Methods |
|---|---|---|
| Certificate | X.509 certificate representation | new, verify, to_pem, to_der, sign |
| Request | Certificate signing request | new, verify, to_pem, sign |
| Store | Certificate trust store | new, add_cert, add_file, verify, set_default_paths |
| Name | Distinguished name | new, to_s, parse |
| Extension | Certificate extension | new, oid, value, critical? |
| ExtensionFactory | Extension creator | new, create_extension |
SSL/TLS Verification Modes
| Mode | Description | Use Case |
|---|---|---|
| VERIFY_NONE | No verification | Never use in production |
| VERIFY_PEER | Verify peer certificate | Standard client mode |
| VERIFY_FAIL_IF_NO_PEER_CERT | Require peer certificate | Server requiring client certs |
| VERIFY_CLIENT_ONCE | Verify client cert once | Server optimization |
Certificate File Formats
| Format | Extension | Encoding | Description |
|---|---|---|---|
| PEM | .pem, .crt, .cer | Base64 ASCII | Text format with BEGIN/END markers |
| DER | .der, .cer | Binary | Binary encoding of certificate |
| PKCS#7 | .p7b, .p7c | Binary or Base64 | Certificate chain container |
| PKCS#12 | .p12, .pfx | Binary | Password-protected certificate and key |
Distinguished Name Attributes
| Attribute | OID | Description | Example |
|---|---|---|---|
| CN | 2.5.4.3 | Common Name | example.com |
| O | 2.5.4.10 | Organization | Example Corp |
| OU | 2.5.4.11 | Organizational Unit | Engineering |
| C | 2.5.4.6 | Country | US |
| ST | 2.5.4.8 | State or Province | California |
| L | 2.5.4.7 | Locality | San Francisco |
Signature Algorithms
| Algorithm | Security | Key Size | Usage |
|---|---|---|---|
| sha256WithRSAEncryption | Strong | 2048+ bits | Current standard |
| sha384WithRSAEncryption | Strong | 3072+ bits | High security |
| sha512WithRSAEncryption | Strong | 4096+ bits | Maximum security |
| ecdsa-with-SHA256 | Strong | 256+ bits | Efficient alternative |
| sha1WithRSAEncryption | Deprecated | Any | Legacy only |
Certificate Validation Errors
| Error Code | Meaning | Common Cause |
|---|---|---|
| V_ERR_CERT_HAS_EXPIRED | Certificate expired | Past not_after date |
| V_ERR_CERT_NOT_YET_VALID | Certificate not yet valid | Before not_before date |
| V_ERR_UNABLE_TO_GET_ISSUER_CERT | Cannot find issuer | Missing intermediate cert |
| V_ERR_SELF_SIGNED_CERT_IN_CHAIN | Self-signed in chain | Untrusted root |
| V_ERR_CERT_REVOKED | Certificate revoked | Listed in CRL or OCSP |
| V_ERR_HOSTNAME_MISMATCH | Hostname mismatch | CN/SAN does not match |