CrackedRuby CrackedRuby

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