Overview
Digital certificates serve as the foundation of public key infrastructure (PKI), enabling secure communication across networks. A digital certificate combines a public key with identifying information about its owner, digitally signed by a certificate authority (CA) to verify authenticity. This mechanism solves the key distribution problem in public key cryptography - how to securely share public keys while ensuring they belong to the claimed entity.
The X.509 standard defines the most widely used certificate format. These certificates contain the subject's public key, identifying information (such as domain name or organization), validity period, and the CA's digital signature. When a client connects to a server using TLS/SSL, the server presents its certificate. The client validates the certificate by checking the CA's signature, confirming the certificate hasn't expired, and verifying the hostname matches the certificate's subject.
require 'openssl'
require 'net/http'
# Fetch and examine a certificate
uri = URI('https://www.example.com')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.start do |connection|
cert = connection.peer_cert
puts "Subject: #{cert.subject}"
puts "Issuer: #{cert.issuer}"
puts "Valid from: #{cert.not_before}"
puts "Valid until: #{cert.not_after}"
puts "Serial: #{cert.serial}"
end
Digital certificates power HTTPS websites, email encryption (S/MIME), code signing, client authentication, and VPNs. The trust model relies on certificate authorities - organizations trusted to verify identities before issuing certificates. Browsers and operating systems maintain lists of trusted root CAs. Any certificate signed by these roots, or by intermediate CAs authorized by roots, inherits that trust.
Key Principles
Digital certificates operate on asymmetric cryptography principles. Each entity possesses a key pair: a private key kept secret and a public key distributed freely. The certificate binds the public key to an identity, preventing impersonation attacks. The mathematical relationship between the keys ensures data encrypted with one key can only be decrypted with its pair.
The X.509 certificate structure contains multiple fields with specific purposes. The subject identifies the certificate holder using distinguished names (DN) with components like common name (CN), organization (O), and country (C). The issuer identifies the CA that signed the certificate. The validity period defines the timeframe during which the certificate should be trusted. The public key contains the actual cryptographic key and algorithm information. The signature proves the CA verified the subject's identity and approved the certificate.
Certificate chains establish trust hierarchically. A root CA certificate is self-signed - its issuer and subject are identical. Root CAs issue intermediate CA certificates, which in turn sign end-entity certificates. This chain structure allows root CAs to remain offline for security while intermediate CAs handle day-to-day operations. Validation requires checking each certificate in the chain back to a trusted root.
require 'openssl'
# Parse a certificate and examine its structure
cert_pem = File.read('certificate.pem')
cert = OpenSSL::X509::Certificate.new(cert_pem)
# Extract subject components
subject = cert.subject.to_a
subject.each do |component|
key, value, type = component
puts "#{key}: #{value}"
end
# Display extensions
cert.extensions.each do |ext|
puts "#{ext.oid}: #{ext.value}"
end
Certificate extensions add functionality beyond basic identification. The Subject Alternative Name (SAN) extension lists additional identities the certificate covers, such as multiple domain names. The Key Usage extension specifies permitted cryptographic operations like digital signatures or key encryption. The Basic Constraints extension indicates whether the certificate can sign other certificates, distinguishing CA certificates from end-entity certificates.
The validation process involves multiple checks. First, verify the signature chain from the end-entity certificate to a trusted root. Each certificate's signature must validate against its issuer's public key. Second, check each certificate's validity period against the current time. Third, verify the end-entity certificate's subject matches the expected identity (hostname for TLS). Fourth, check revocation status - whether any certificate in the chain has been revoked before expiration.
Certificate revocation addresses compromised or incorrectly issued certificates. Two primary mechanisms exist: Certificate Revocation Lists (CRL) and Online Certificate Status Protocol (OCSP). CRLs are periodically published lists of revoked certificate serial numbers. OCSP provides real-time revocation checking by querying a responder. Modern browsers increasingly use OCSP stapling, where servers attach signed OCSP responses to TLS handshakes, improving performance and privacy.
Ruby Implementation
Ruby's OpenSSL library provides comprehensive certificate handling through the OpenSSL::X509 module. The library wraps the OpenSSL C library, exposing certificate creation, parsing, validation, and chain building functionality.
Creating a self-signed certificate requires generating a key pair, instantiating a certificate object, setting its fields, and signing it with the private key:
require 'openssl'
# Generate a 2048-bit RSA key pair
key = OpenSSL::PKey::RSA.new(2048)
# Create a certificate
cert = OpenSSL::X509::Certificate.new
cert.version = 2 # X.509 v3
cert.serial = 1
cert.subject = OpenSSL::X509::Name.parse("/CN=localhost/O=Development/C=US")
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 validity
# 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 the certificate
cert.sign(key, OpenSSL::Digest::SHA256.new)
# Save to files
File.write('certificate.pem', cert.to_pem)
File.write('private_key.pem', key.to_pem)
Reading and parsing existing certificates converts PEM or DER encoded data into certificate objects. Ruby handles both formats transparently:
# Read from PEM file
cert_pem = File.read('certificate.pem')
cert = OpenSSL::X509::Certificate.new(cert_pem)
# Read from DER binary
cert_der = File.binread('certificate.der')
cert = OpenSSL::X509::Certificate.new(cert_der)
# Extract specific fields
puts "Serial Number: #{cert.serial.to_i}"
puts "Signature Algorithm: #{cert.signature_algorithm}"
# Check if certificate is a CA
basic_constraints = cert.extensions.find { |ext| ext.oid == "basicConstraints" }
is_ca = basic_constraints && basic_constraints.value.include?("CA:TRUE")
puts "Is CA: #{is_ca}"
Certificate validation in Ruby requires building and verifying the certificate chain. The OpenSSL::X509::Store class manages trusted certificates and performs validation:
require 'openssl'
def validate_certificate(cert_path, ca_cert_path)
cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
ca_cert = OpenSSL::X509::Certificate.new(File.read(ca_cert_path))
# Create a certificate store
store = OpenSSL::X509::Store.new
store.add_cert(ca_cert)
# Verify the certificate
if store.verify(cert)
puts "Certificate is valid"
true
else
puts "Certificate validation failed: #{store.error_string}"
false
end
end
# Load system default certificates
store = OpenSSL::X509::Store.new
store.set_default_paths
# Add custom CA certificates
custom_ca = OpenSSL::X509::Certificate.new(File.read('custom_ca.pem'))
store.add_cert(custom_ca)
# Verify a certificate chain
cert = OpenSSL::X509::Certificate.new(File.read('server_cert.pem'))
result = store.verify(cert)
puts "Verification result: #{result}"
Ruby's HTTP libraries integrate certificate validation automatically. Configuring SSL/TLS options controls certificate verification behavior:
require 'net/http'
require 'uri'
uri = URI('https://secure.example.com')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
# Default: verify certificates against system CA store
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
# Use custom CA certificate
http.ca_file = '/path/to/ca_bundle.crt'
# Or load from directory
http.ca_path = '/etc/ssl/certs'
# Disable verification (dangerous - only for development)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
# Verify certificate hostname
http.verify_hostname = true
response = http.get(uri.path)
The OpenSSL library also supports certificate signing requests (CSR), which entities submit to CAs for certificate issuance:
# Generate CSR
key = OpenSSL::PKey::RSA.new(2048)
csr = OpenSSL::X509::Request.new
csr.version = 0
csr.subject = OpenSSL::X509::Name.parse("/CN=www.example.com/O=Example Corp/C=US")
csr.public_key = key.public_key
# Add extensions to CSR
extensions = OpenSSL::ASN1::Set([
OpenSSL::ASN1::Sequence([
OpenSSL::X509::Extension.new("subjectAltName",
"DNS:www.example.com,DNS:example.com")
])
])
csr.add_attribute(OpenSSL::X509::Attribute.new("extReq", extensions))
# Sign the CSR
csr.sign(key, OpenSSL::Digest::SHA256.new)
File.write('request.csr', csr.to_pem)
Security Implications
Certificate security depends on protecting private keys and validating certificates correctly. Compromised private keys allow attackers to impersonate certificate owners, decrypt encrypted communications, and sign malicious content. Private keys must never be transmitted over networks, committed to version control, or stored without encryption.
The certificate trust model assumes CA integrity. Compromised or malicious CAs can issue fraudulent certificates for any domain. Several incidents have exposed CA vulnerabilities, leading to the development of Certificate Transparency (CT) - a public log system where CAs must record issued certificates. Browsers reject certificates not recorded in CT logs, making rogue certificates detectable.
Man-in-the-middle (MITM) attacks exploit certificate validation failures. An attacker intercepts traffic and presents their own certificate. If the client doesn't properly validate the certificate chain, hostname, and expiration, the attack succeeds. Applications must enforce strict certificate validation and never disable checks in production environments.
# Secure certificate validation
require 'net/http'
def secure_request(url)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
# Enforce peer verification
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
# Verify hostname matches certificate
http.verify_hostname = true
# Use system CA certificates
http.cert_store = OpenSSL::X509::Store.new
http.cert_store.set_default_paths
# Optional: certificate pinning for known hosts
http.verify_callback = proc do |verify_ok, store_context|
if verify_ok
cert = store_context.current_cert
# Verify certificate fingerprint matches expected value
expected_fingerprint = "A1:B2:C3:..." # Known good fingerprint
actual_fingerprint = Digest::SHA256.hexdigest(cert.to_der)
verify_ok = actual_fingerprint == expected_fingerprint
end
verify_ok
end
http.get(uri.path)
end
Certificate pinning provides additional security by validating that a server's certificate matches a known, expected certificate or public key. This prevents attacks even if a CA is compromised. However, pinning complicates certificate rotation and can cause outages if certificates change unexpectedly.
Expired certificates present security risks. After expiration, the CA no longer guarantees the certificate's validity. Continued use allows attackers who compromised the private key after expiration to impersonate the certificate owner. Automated certificate renewal systems like Let's Encrypt address this by issuing short-lived certificates with automated renewal.
Self-signed certificates bypass the CA trust model. While acceptable for internal development, self-signed certificates should never be used in production for public-facing services. They provide encryption but no authentication - clients cannot verify the certificate owner's identity. Users must manually trust self-signed certificates, which conditions them to ignore security warnings.
Certificate revocation mechanisms have implementation challenges. CRL distribution is slow and bandwidth-intensive. OCSP adds latency to each connection and reveals browsing patterns to the CA. OCSP stapling improves privacy but requires server support. Many clients implement soft-fail policies where revocation check failures don't prevent connections, reducing security.
# Check certificate revocation status
require 'openssl'
require 'net/http'
def check_ocsp_status(cert, issuer_cert)
# Extract OCSP responder URL from certificate
authority_info = cert.extensions.find { |e| e.oid == "authorityInfoAccess" }
return nil unless authority_info
ocsp_url = authority_info.value.match(/OCSP - URI:(.*)/)[1]
# Create OCSP request
cert_id = OpenSSL::OCSP::CertificateId.new(cert, issuer_cert)
request = OpenSSL::OCSP::Request.new
request.add_certid(cert_id)
# Send request to OCSP responder
uri = URI(ocsp_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
response = http.post(uri.path, request.to_der,
'Content-Type' => 'application/ocsp-request')
# Parse response
ocsp_response = OpenSSL::OCSP::Response.new(response.body)
basic_response = ocsp_response.basic
# Verify response signature
store = OpenSSL::X509::Store.new
store.add_cert(issuer_cert)
if basic_response.verify([], store)
status = basic_response.status.first
case status[1]
when OpenSSL::OCSP::V_CERTSTATUS_GOOD
:good
when OpenSSL::OCSP::V_CERTSTATUS_REVOKED
:revoked
when OpenSSL::OCSP::V_CERTSTATUS_UNKNOWN
:unknown
end
else
:invalid_response
end
end
Practical Examples
Web servers use certificates to establish HTTPS connections. Configuring a Ruby web server requires loading the certificate and private key:
require 'webrick'
require 'webrick/https'
# Load certificate and key
cert = OpenSSL::X509::Certificate.new(File.read('server_cert.pem'))
key = OpenSSL::PKey::RSA.new(File.read('server_key.pem'))
# Create HTTPS server
server = WEBrick::HTTPServer.new(
Port: 8443,
SSLEnable: true,
SSLCertificate: cert,
SSLPrivateKey: key,
SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE
)
server.mount_proc('/') do |req, res|
res.body = 'Secure connection established'
end
trap('INT') { server.shutdown }
server.start
Client certificate authentication requires servers to verify client-presented certificates. This provides mutual authentication where both parties prove their identities:
# Server with client certificate verification
server = WEBrick::HTTPServer.new(
Port: 8443,
SSLEnable: true,
SSLCertificate: OpenSSL::X509::Certificate.new(File.read('server_cert.pem')),
SSLPrivateKey: OpenSSL::PKey::RSA.new(File.read('server_key.pem')),
SSLVerifyClient: OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT,
SSLCACertificateFile: 'client_ca.pem'
)
server.mount_proc('/') do |req, res|
client_cert = req.client_cert
if client_cert
res.body = "Authenticated as: #{client_cert.subject}"
else
res.status = 401
res.body = 'Client certificate required'
end
end
# Client presenting certificate
uri = URI('https://localhost:8443/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.cert = OpenSSL::X509::Certificate.new(File.read('client_cert.pem'))
http.key = OpenSSL::PKey::RSA.new(File.read('client_key.pem'))
http.ca_file = 'server_ca.pem'
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
response = http.get(uri.path)
puts response.body
Email encryption with S/MIME uses certificates to encrypt messages and verify sender identities. The sender encrypts using the recipient's public key from their certificate:
require 'openssl'
require 'mail'
# Load certificates and keys
sender_cert = OpenSSL::X509::Certificate.new(File.read('sender_cert.pem'))
sender_key = OpenSSL::PKey::RSA.new(File.read('sender_key.pem'))
recipient_cert = OpenSSL::X509::Certificate.new(File.read('recipient_cert.pem'))
# Create and sign email
mail = Mail.new do
from 'sender@example.com'
to 'recipient@example.com'
subject 'Encrypted Message'
body 'Confidential information'
end
# Sign the message
signed = OpenSSL::PKCS7.sign(
sender_cert,
sender_key,
mail.to_s,
[],
OpenSSL::PKCS7::DETACHED
)
# Encrypt for recipient
encrypted = OpenSSL::PKCS7.encrypt(
[recipient_cert],
signed.to_pem,
OpenSSL::Cipher.new('AES-256-CBC'),
OpenSSL::PKCS7::BINARY
)
# Send encrypted message
secure_mail = Mail.new
secure_mail.from = 'sender@example.com'
secure_mail.to = 'recipient@example.com'
secure_mail.content_type = 'application/pkcs7-mime; smime-type=enveloped-data'
secure_mail.body = encrypted.to_der
secure_mail.deliver
Automated certificate provisioning with Let's Encrypt uses the ACME protocol to obtain free certificates. The acme-client gem implements this protocol:
require 'acme-client'
require 'fileutils'
# Initialize ACME client
private_key = OpenSSL::PKey::RSA.new(4096)
client = Acme::Client.new(
private_key: private_key,
directory: 'https://acme-v02.api.letsencrypt.org/directory'
)
# Register account
account = client.new_account(
contact: 'mailto:admin@example.com',
terms_of_service_agreed: true
)
# Request certificate for domain
order = client.new_order(identifiers: ['www.example.com'])
# Complete HTTP-01 challenge
authorization = order.authorizations.first
challenge = authorization.http
filename = File.join('.well-known', 'acme-challenge', challenge.token)
FileUtils.mkdir_p(File.dirname(filename))
File.write(filename, challenge.file_content)
# Trigger validation
challenge.request_validation
sleep(2) while challenge.reload.status == 'pending'
if challenge.status == 'valid'
# Generate CSR
csr_key = OpenSSL::PKey::RSA.new(2048)
csr = Acme::Client::CertificateRequest.new(
private_key: csr_key,
subject: { common_name: 'www.example.com' }
)
# Finalize order
order.finalize(csr: csr)
sleep(1) while order.reload.status == 'processing'
# Download certificate
certificate = order.certificate
File.write('certificate.pem', certificate)
File.write('private_key.pem', csr_key.to_pem)
puts "Certificate obtained successfully"
else
puts "Challenge failed: #{challenge.error}"
end
Common Pitfalls
Certificate expiration causes service outages when certificates expire without renewal. Certificates typically have validity periods of 90 days (Let's Encrypt) to 1 year. Organizations must implement monitoring and automated renewal processes:
require 'openssl'
require 'net/smtp'
def check_certificate_expiration(cert_path, warning_days: 30)
cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
days_until_expiry = ((cert.not_after - Time.now) / 86400).to_i
if days_until_expiry < 0
status = :expired
message = "Certificate expired #{days_until_expiry.abs} days ago"
elsif days_until_expiry < warning_days
status = :warning
message = "Certificate expires in #{days_until_expiry} days"
else
status = :ok
message = "Certificate valid for #{days_until_expiry} days"
end
{ status: status, days: days_until_expiry, message: message }
end
# Monitor multiple certificates
certificates = {
'example.com' => '/path/to/example.pem',
'api.example.com' => '/path/to/api.pem'
}
certificates.each do |domain, path|
result = check_certificate_expiration(path)
if result[:status] != :ok
puts "WARNING - #{domain}: #{result[:message]}"
# Send alert email
end
end
Incomplete certificate chains cause validation failures. Servers must send the complete chain from the end-entity certificate through all intermediates to a root CA. Missing intermediate certificates prevent clients from building a valid path:
# Incorrect: sending only the server certificate
cert = OpenSSL::X509::Certificate.new(File.read('server_cert.pem'))
# Correct: concatenate server cert and intermediate certs
full_chain = File.read('server_cert.pem') +
File.read('intermediate_cert.pem')
cert = OpenSSL::X509::Certificate.new(full_chain)
# Verify chain completeness
def verify_chain_complete(cert_path)
content = File.read(cert_path)
certs = content.scan(/-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m)
certificates = certs.map { |pem| OpenSSL::X509::Certificate.new(pem) }
# Check if chain reaches a self-signed root
last_cert = certificates.last
is_complete = last_cert.issuer == last_cert.subject
puts "Chain contains #{certificates.length} certificates"
puts "Chain complete: #{is_complete}"
certificates.each_with_index do |cert, i|
puts "#{i + 1}. #{cert.subject}"
end
is_complete
end
Hostname verification failures occur when the certificate's subject doesn't match the requested hostname. The certificate's Common Name (CN) or Subject Alternative Name (SAN) must match exactly:
def verify_hostname(cert, hostname)
# Check Common Name
cn = cert.subject.to_a.find { |e| e[0] == 'CN' }
return true if cn && cn[1] == hostname
# Check Subject Alternative Names
san_ext = cert.extensions.find { |e| e.oid == 'subjectAltName' }
if san_ext
san_values = san_ext.value.split(',').map(&:strip)
san_values.each do |san|
if san.start_with?('DNS:')
dns_name = san.sub('DNS:', '')
# Handle wildcards
if dns_name.start_with?('*.')
pattern = dns_name.sub('*.', '.*\.')
return true if hostname.match?(/^#{pattern}$/)
else
return true if dns_name == hostname
end
end
end
end
false
end
# Example usage
cert = OpenSSL::X509::Certificate.new(File.read('cert.pem'))
valid = verify_hostname(cert, 'www.example.com')
puts "Hostname verification: #{valid}"
Private key mismatches prevent certificate usage. The certificate's public key must correspond to the private key used for TLS handshakes:
def verify_key_pair(cert_path, key_path)
cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
key = OpenSSL::PKey::RSA.new(File.read(key_path))
# Compare public key modulus
cert_modulus = cert.public_key.n
key_modulus = key.public_key.n
if cert_modulus == key_modulus
puts "Certificate and key match"
true
else
puts "Certificate and key DO NOT match"
false
end
rescue OpenSSL::PKey::RSAError
puts "Invalid private key format"
false
rescue OpenSSL::X509::CertificateError
puts "Invalid certificate format"
false
end
Permission and file access issues cause runtime failures. Private keys require restricted file permissions to prevent unauthorized access:
require 'fileutils'
def secure_key_storage(key_path, key_content)
# Write key with restrictive permissions
File.write(key_path, key_content)
File.chmod(0600, key_path) # Read/write for owner only
# Verify permissions
stat = File.stat(key_path)
mode = stat.mode & 0777
if mode != 0600
warn "WARNING: Key file has insecure permissions: #{mode.to_s(8)}"
end
end
# Check for common permission issues
def audit_certificate_files(cert_dir)
issues = []
Dir.glob("#{cert_dir}/*.{key,pem}") do |file|
stat = File.stat(file)
mode = stat.mode & 0777
if file.end_with?('.key') && mode != 0600
issues << "#{file}: Private key too permissive (#{mode.to_s(8)})"
end
if !stat.owned_by_current_user?
issues << "#{file}: Not owned by current user"
end
end
issues
end
Tools & Ecosystem
OpenSSL command-line tools provide certificate generation, conversion, and inspection capabilities. These commands handle common certificate operations outside application code:
# Generate self-signed certificate
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
# Generate CSR
openssl req -new -key private.key -out request.csr
# View certificate details
openssl x509 -in certificate.pem -text -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
# Verify certificate chain
openssl verify -CAfile ca-bundle.pem certificate.pem
# Check certificate and key match
openssl x509 -in cert.pem -modulus -noout | openssl md5
openssl rsa -in key.pem -modulus -noout | openssl md5
# Connect and view server certificate
openssl s_client -connect example.com:443 -showcerts
The openssl gem provides Ruby bindings to OpenSSL library functionality. This standard library gem requires no additional installation and handles cryptographic operations:
require 'openssl'
# Available classes
OpenSSL::X509::Certificate # Certificate operations
OpenSSL::X509::Request # Certificate signing requests
OpenSSL::X509::Store # Certificate store and validation
OpenSSL::X509::Name # Distinguished names
OpenSSL::PKey::RSA # RSA key operations
OpenSSL::PKey::EC # Elliptic curve keys
OpenSSL::PKCS12 # PKCS#12 archives
OpenSSL::PKCS7 # S/MIME operations
Let's Encrypt provides automated certificate issuance through the ACME protocol. The service issues certificates valid for 90 days, encouraging automation. Several Ruby gems implement ACME clients:
# Using acme-client gem
gem 'acme-client'
require 'acme-client'
# Set up client
client = Acme::Client.new(
private_key: OpenSSL::PKey::RSA.new(4096),
directory: Acme::Client::LETSENCRYPT_PRODUCTION_URL
)
# The certbot tool provides command-line automation
# certbot certonly --webroot -w /var/www/html -d example.com
# Using dehydrated shell script for simple automation
# ./dehydrated --cron --domain example.com
Certificate management platforms handle certificate lifecycle operations at scale. These tools track expiration dates, automate renewal, and centralize certificate storage. Popular options include:
- HashiCorp Vault: Provides PKI secrets engine for certificate generation
- cert-manager: Kubernetes-native certificate management
- AWS Certificate Manager: Managed certificates for AWS resources
- Smallstep: Private certificate authority and automation tools
Ruby integration with these platforms varies by tool:
# Vault PKI integration
require 'vault'
Vault.address = 'https://vault.example.com'
Vault.token = ENV['VAULT_TOKEN']
# Request certificate from Vault PKI backend
result = Vault.logical.write(
'pki/issue/server-role',
common_name: 'app.example.com',
ttl: '720h'
)
certificate = result.data[:certificate]
private_key = result.data[:private_key]
ca_chain = result.data[:ca_chain]
Certificate transparency logs provide public records of issued certificates. CT monitors detect mis-issued certificates and unauthorized CAs. Tools query CT logs to find certificates for domains:
# Using crt.sh web interface
curl 'https://crt.sh/?q=example.com&output=json'
# Using certstream for real-time monitoring
gem 'certstream'
The Ruby SSL ecosystem includes gems for specific use cases:
# ssl_scan - analyze SSL/TLS configuration
gem 'ssl_scan'
# sslscan - security assessment
require 'sslscan'
scanner = SSLScan::Scanner.new('example.com')
results = scanner.scan
# puma - production web server with SSL support
gem 'puma'
# config/puma.rb
ssl_bind '0.0.0.0', '443', {
cert: 'path/to/cert.pem',
key: 'path/to/key.pem'
}
Reference
Certificate Fields
| Field | Description | Example Value |
|---|---|---|
| Version | X.509 version number | 2 (v3) |
| Serial Number | Unique identifier within CA | 04:D2:1F:C3:... |
| Signature Algorithm | Algorithm used for signing | SHA256withRSA |
| Issuer | CA that issued certificate | CN=DigiCert TLS RSA SHA256 2020 CA1 |
| Validity Not Before | Start of validity period | 2024-01-15 00:00:00 UTC |
| Validity Not After | End of validity period | 2025-01-14 23:59:59 UTC |
| Subject | Certificate holder identity | CN=www.example.com, O=Example Corp |
| Subject Public Key Info | Public key and algorithm | RSA 2048-bit |
| Extensions | Additional certificate features | Subject Alternative Name, Key Usage |
| Signature | CA's digital signature | Binary signature data |
Common Extensions
| Extension | Purpose | Typical Values |
|---|---|---|
| Subject Alternative Name | Additional identities | DNS:example.com, DNS:www.example.com |
| Key Usage | Permitted key operations | Digital Signature, Key Encipherment |
| Extended Key Usage | Specific usage purposes | TLS Web Server Authentication |
| Basic Constraints | CA or end-entity designation | CA:FALSE or CA:TRUE, pathlen:0 |
| Authority Key Identifier | Links to issuer's key | keyid:A1:B2:C3:... |
| Subject Key Identifier | Certificate's key identifier | D4:E5:F6:... |
| CRL Distribution Points | Revocation list locations | URI:http://crl.example.com/root.crl |
| Authority Information Access | CA and OCSP URLs | OCSP - URI:http://ocsp.example.com |
File Formats
| Format | Extension | Encoding | Usage |
|---|---|---|---|
| PEM | .pem, .crt, .cer | Base64 ASCII | Most common, text readable |
| DER | .der, .cer | Binary | Compact, Windows native |
| PKCS#7 | .p7b, .p7c | Base64 or binary | Certificate chains |
| PKCS#12 | .pfx, .p12 | Binary | Certificate with private key |
Ruby OpenSSL API Reference
| Class | Primary Methods | Purpose |
|---|---|---|
| X509::Certificate | new, to_pem, to_der, subject, issuer, not_before, not_after | Certificate operations |
| X509::Request | new, sign, verify, subject | CSR handling |
| X509::Store | new, add_cert, verify, set_default_paths | Certificate validation |
| X509::Name | parse, to_s, to_a | Distinguished name handling |
| PKey::RSA | new, public_key, private?, export | RSA key operations |
| PKey::EC | new, generate_key, group | Elliptic curve keys |
Validation Error Codes
| OpenSSL Error | Constant | Meaning |
|---|---|---|
| Certificate has expired | V_ERR_CERT_HAS_EXPIRED | Past not_after date |
| Certificate is not yet valid | V_ERR_CERT_NOT_YET_VALID | Before not_before date |
| Unable to get issuer certificate | V_ERR_UNABLE_TO_GET_ISSUER_CERT | Missing CA certificate |
| Self signed certificate | V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT | Root not trusted |
| Self signed certificate in chain | V_ERR_SELF_SIGNED_CERT_IN_CHAIN | Intermediate not trusted |
| Certificate signature failure | V_ERR_CERT_SIGNATURE_FAILURE | Invalid signature |
| Certificate revoked | V_ERR_CERT_REVOKED | Listed in CRL/OCSP |
Common Ruby Patterns
# Load certificate from file
cert = OpenSSL::X509::Certificate.new(File.read('cert.pem'))
# Create certificate store with system CAs
store = OpenSSL::X509::Store.new
store.set_default_paths
# Validate certificate
result = store.verify(cert)
# Extract certificate fingerprint
fingerprint = Digest::SHA256.hexdigest(cert.to_der)
# Check expiration
days_remaining = ((cert.not_after - Time.now) / 86400).to_i
# Compare certificate and key
cert.public_key.to_pem == private_key.public_key.to_pem