Overview
Ruby's OpenSSL library provides SSL/TLS functionality through bindings to the OpenSSL C library. The primary classes include OpenSSL::SSL::SSLContext
for configuration, OpenSSL::SSL::SSLSocket
for encrypted connections, and OpenSSL::X509::Certificate
for certificate handling.
SSL/TLS in Ruby operates through context objects that define security parameters, cipher suites, and certificate verification settings. These contexts create encrypted sockets that wrap standard TCP connections, providing transparent encryption for network communication.
require 'openssl'
require 'socket'
# Basic SSL context creation
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.ca_file = '/etc/ssl/certs/ca-certificates.crt'
The Ruby OpenSSL implementation supports both client and server configurations. Client connections typically verify server certificates against trusted certificate authorities, while server configurations require certificate and private key pairs for identity verification.
# Client SSL socket
tcp_socket = TCPSocket.new('example.com', 443)
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.connect
Ruby's SSL implementation handles protocol negotiation automatically, supporting TLS 1.0 through TLS 1.3 depending on the underlying OpenSSL version. The library manages handshakes, certificate validation, and cipher suite selection based on context configuration.
Basic Usage
SSL/TLS connections in Ruby begin with creating an SSL context that defines security parameters. The context specifies verification modes, trusted certificate authorities, and cipher preferences.
require 'openssl'
require 'socket'
require 'net/http'
# Create SSL context with peer verification
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.ca_file = '/etc/ssl/certs/ca-certificates.crt'
# Establish encrypted connection
tcp_socket = TCPSocket.new('httpbin.org', 443)
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.connect
# Send HTTP request
ssl_socket.write("GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n")
response = ssl_socket.read(1024)
ssl_socket.close
Net::HTTP provides higher-level SSL support with automatic context management. The library handles SSL configuration transparently for HTTPS requests.
require 'net/http'
uri = URI('https://httpbin.org/get')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
response = http.get(uri.path)
puts response.body
Server SSL configurations require certificate and private key files. The context loads these credentials and configures the server for incoming encrypted connections.
require 'openssl'
require 'socket'
# Load server certificate and private key
cert = OpenSSL::X509::Certificate.new(File.read('server.crt'))
key = OpenSSL::PKey::RSA.new(File.read('server.key'))
# Create server SSL context
context = OpenSSL::SSL::SSLContext.new
context.cert = cert
context.key = key
context.verify_mode = OpenSSL::SSL::VERIFY_NONE
# Create SSL server
tcp_server = TCPServer.new(8443)
ssl_server = OpenSSL::SSL::SSLServer.new(tcp_server, context)
# Accept encrypted connections
loop do
ssl_socket = ssl_server.accept
data = ssl_socket.read
ssl_socket.write("HTTP/1.1 200 OK\r\n\r\nHello SSL")
ssl_socket.close
end
Certificate verification settings control how Ruby validates peer certificates. VERIFY_PEER
requires valid certificates, while VERIFY_NONE
accepts any certificate including self-signed ones.
# Strict verification (production)
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.verify_hostname = true
# Permissive verification (development)
context.verify_mode = OpenSSL::SSL::VERIFY_NONE
Advanced Usage
SSL contexts support detailed cipher suite configuration for specific security requirements. Ruby exposes cipher selection through string patterns and explicit cipher lists.
require 'openssl'
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
# Configure cipher suites
context.ciphers = 'ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS'
# Set minimum protocol version
context.min_version = OpenSSL::SSL::TLS1_2_VERSION
context.max_version = OpenSSL::SSL::TLS1_3_VERSION
# Configure cipher preferences
context.options |= OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE
Certificate chain validation requires loading intermediate certificates. Ruby validates the complete chain from server certificate to trusted root authority.
# Load certificate chain
cert_chain = []
cert_chain << OpenSSL::X509::Certificate.new(File.read('server.crt'))
cert_chain << OpenSSL::X509::Certificate.new(File.read('intermediate.crt'))
context.cert = cert_chain.first
context.extra_chain_cert = cert_chain[1..-1]
# Custom verification callback
context.verify_callback = proc do |preverify_ok, store_context|
if preverify_ok
cert = store_context.current_cert
puts "Certificate verified: #{cert.subject}"
true
else
puts "Certificate verification failed: #{store_context.error_string}"
false
end
end
Session management optimizes SSL performance by reusing negotiated parameters across connections. Ruby supports both session tickets and session IDs.
# Enable session reuse
context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
context.session_id_context = 'ruby_ssl_session'
# Configure session timeout
context.session_timeout = 300
# Session callback for custom storage
context.session_new_cb = proc do |ssl_socket, session|
session_id = session.id.unpack('H*').first
session_data = session.to_der
# Store session data in external cache
Redis.current.setex("ssl_session:#{session_id}", 300, session_data)
end
Client certificate authentication enables mutual TLS authentication. Both client and server present certificates for bidirectional identity verification.
# Client certificate configuration
client_cert = OpenSSL::X509::Certificate.new(File.read('client.crt'))
client_key = OpenSSL::PKey::RSA.new(File.read('client.key'))
context.cert = client_cert
context.key = client_key
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
# Server requiring client certificates
server_context.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
server_context.ca_file = '/path/to/client_ca.crt'
Error Handling & Debugging
SSL connections generate specific exception classes that require targeted error handling. Ruby raises different exceptions for connection failures, certificate problems, and protocol errors.
require 'openssl'
require 'socket'
def secure_connect(hostname, port)
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
tcp_socket = TCPSocket.new(hostname, port)
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.connect
rescue OpenSSL::SSL::SSLError => e
case e.message
when /certificate verify failed/
puts "Certificate validation failed: #{e.message}"
# Handle certificate issues
when /wrong version number/
puts "SSL protocol mismatch: #{e.message}"
# Handle protocol issues
when /handshake failure/
puts "SSL handshake failed: #{e.message}"
# Handle cipher/protocol negotiation failures
else
puts "General SSL error: #{e.message}"
end
nil
rescue SocketError => e
puts "Network connection failed: #{e.message}"
nil
ensure
tcp_socket&.close
end
Certificate validation errors provide specific error codes and descriptions. Ruby exposes verification results through the SSL context's error information.
def diagnose_certificate_error(ssl_socket)
verify_result = ssl_socket.verify_result
case verify_result
when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED
puts "Certificate has expired"
when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID
puts "Certificate is not yet valid"
when OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN
puts "Self-signed certificate in chain"
when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT
puts "Unable to get issuer certificate"
when OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH
puts "Hostname does not match certificate"
else
puts "Certificate verification error: #{verify_result}"
end
# Get peer certificate for inspection
peer_cert = ssl_socket.peer_cert
if peer_cert
puts "Subject: #{peer_cert.subject}"
puts "Issuer: #{peer_cert.issuer}"
puts "Valid from: #{peer_cert.not_before}"
puts "Valid until: #{peer_cert.not_after}"
end
end
Protocol debugging requires examining negotiated parameters and connection state. Ruby provides methods to inspect cipher suites, protocol versions, and session information.
def debug_ssl_connection(ssl_socket)
puts "SSL Version: #{ssl_socket.ssl_version}"
puts "Cipher: #{ssl_socket.cipher.first}"
puts "Cipher bits: #{ssl_socket.cipher[2]}"
# Display certificate chain
cert_chain = ssl_socket.peer_cert_chain
cert_chain.each_with_index do |cert, index|
puts "Certificate #{index + 1}:"
puts " Subject: #{cert.subject}"
puts " Issuer: #{cert.issuer}"
puts " Serial: #{cert.serial}"
end
# Session information
session = ssl_socket.session
puts "Session ID: #{session.id.unpack('H*').first}"
puts "Session timeout: #{session.timeout}"
end
Production Patterns
Production SSL configurations require careful security parameter tuning and certificate management. Ruby applications typically load certificates from environment variables or secure configuration files.
class SSLService
def initialize
@context = create_ssl_context
end
private
def create_ssl_context
context = OpenSSL::SSL::SSLContext.new
# Security hardening
context.min_version = OpenSSL::SSL::TLS1_2_VERSION
context.options |= OpenSSL::SSL::OP_NO_SSLv2
context.options |= OpenSSL::SSL::OP_NO_SSLv3
context.options |= OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE
# Cipher suite selection
context.ciphers = secure_cipher_list
# Certificate configuration
configure_certificates(context)
# OCSP stapling
context.options |= OpenSSL::SSL::OP_TLSEXT_PADDING if defined?(OpenSSL::SSL::OP_TLSEXT_PADDING)
context
end
def secure_cipher_list
[
'ECDHE+AESGCM',
'ECDHE+CHACHA20',
'DHE+AESGCM',
'DHE+CHACHA20',
'!aNULL',
'!eNULL',
'!EXPORT',
'!DES',
'!RC4',
'!MD5',
'!PSK',
'!SRP',
'!CAMELLIA'
].join(':')
end
def configure_certificates(context)
cert_file = ENV['SSL_CERT_FILE'] || '/etc/ssl/certs/server.crt'
key_file = ENV['SSL_KEY_FILE'] || '/etc/ssl/private/server.key'
ca_file = ENV['SSL_CA_FILE'] || '/etc/ssl/certs/ca-certificates.crt'
context.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
context.key = OpenSSL::PKey::RSA.new(File.read(key_file))
context.ca_file = ca_file
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
end
end
HTTP client libraries require SSL configuration for production reliability. Net::HTTP settings control timeouts, verification, and retry behavior.
class HTTPSClient
def initialize(base_url, options = {})
@uri = URI(base_url)
@http = Net::HTTP.new(@uri.host, @uri.port)
configure_ssl(options)
configure_timeouts(options)
end
def get(path, headers = {})
request = Net::HTTP::Get.new(path)
headers.each { |key, value| request[key] = value }
@http.request(request)
rescue Net::ReadTimeout, Net::OpenTimeout => e
retry_count ||= 0
retry_count += 1
retry if retry_count < 3
raise
end
private
def configure_ssl(options)
@http.use_ssl = @uri.scheme == 'https'
return unless @http.use_ssl?
@http.verify_mode = options[:verify_mode] || OpenSSL::SSL::VERIFY_PEER
@http.ca_file = options[:ca_file] if options[:ca_file]
@http.cert = options[:cert] if options[:cert]
@http.key = options[:key] if options[:key]
@http.ssl_version = options[:ssl_version] if options[:ssl_version]
end
def configure_timeouts(options)
@http.open_timeout = options[:open_timeout] || 10
@http.read_timeout = options[:read_timeout] || 30
@http.write_timeout = options[:write_timeout] || 30
end
end
Connection pooling improves SSL performance by reusing established connections. Ruby's connection pools manage SSL context sharing and connection lifecycle.
require 'connection_pool'
class SSLConnectionPool
def initialize(size: 5, timeout: 5)
@pool = ConnectionPool.new(size: size, timeout: timeout) do
create_connection
end
end
def with_connection(&block)
@pool.with(&block)
end
private
def create_connection
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.ca_file = '/etc/ssl/certs/ca-certificates.crt'
# Enable session reuse
context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
tcp_socket = TCPSocket.new(ENV['API_HOST'], ENV['API_PORT'])
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.connect
ssl_socket
end
end
# Usage
pool = SSLConnectionPool.new
pool.with_connection do |ssl_socket|
ssl_socket.write("GET /api/data HTTP/1.1\r\n\r\n")
response = ssl_socket.read(1024)
end
Common Pitfalls
Certificate verification failures occur when Ruby cannot validate the certificate chain. The most common issue involves missing intermediate certificates or incorrect CA certificate configurations.
# PITFALL: Missing intermediate certificates
# This fails when server doesn't send complete chain
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.ca_file = '/etc/ssl/certs/ca-certificates.crt'
# SOLUTION: Handle incomplete chains
def handle_incomplete_chain(hostname, port)
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
# Custom verification callback
context.verify_callback = proc do |preverify_ok, store_context|
if !preverify_ok && store_context.error == OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT
# Attempt to fetch missing intermediate certificate
fetch_missing_intermediate(store_context.current_cert)
else
preverify_ok
end
end
context
end
Protocol version negotiation problems arise when clients and servers support different SSL/TLS versions. Ruby's automatic negotiation sometimes fails with legacy systems.
# PITFALL: Hard-coded protocol versions
context.ssl_version = :TLSv1_2 # Fails with TLS 1.3 servers
# SOLUTION: Use version ranges
context.min_version = OpenSSL::SSL::TLS1_2_VERSION
context.max_version = OpenSSL::SSL::TLS1_3_VERSION
# PITFALL: Ignoring protocol errors
begin
ssl_socket.connect
rescue OpenSSL::SSL::SSLError
# Silently ignoring errors
end
# SOLUTION: Graceful protocol fallback
def connect_with_fallback(hostname, port)
protocols = [
[OpenSSL::SSL::TLS1_3_VERSION, OpenSSL::SSL::TLS1_3_VERSION],
[OpenSSL::SSL::TLS1_2_VERSION, OpenSSL::SSL::TLS1_2_VERSION],
[OpenSSL::SSL::TLS1_1_VERSION, OpenSSL::SSL::TLS1_1_VERSION]
]
protocols.each do |min_version, max_version|
context = OpenSSL::SSL::SSLContext.new
context.min_version = min_version
context.max_version = max_version
begin
tcp_socket = TCPSocket.new(hostname, port)
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.connect
return ssl_socket
rescue OpenSSL::SSL::SSLError => e
next if e.message.include?('wrong version number')
raise
end
end
raise "Unable to establish SSL connection with any supported protocol"
end
Memory leaks occur when SSL sockets and contexts are not properly closed. Ruby's garbage collector cannot reclaim OpenSSL resources without explicit cleanup.
# PITFALL: Resource leaks
def leaky_ssl_request(url)
uri = URI(url)
tcp_socket = TCPSocket.new(uri.host, uri.port)
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.connect
# Missing cleanup - resources leak
ssl_socket.read(1024)
end
# SOLUTION: Proper resource management
def clean_ssl_request(url)
uri = URI(url)
tcp_socket = nil
ssl_socket = nil
begin
tcp_socket = TCPSocket.new(uri.host, uri.port)
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.connect
ssl_socket.read(1024)
ensure
ssl_socket&.close
tcp_socket&.close
end
end
# BETTER: Use block form when available
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.get(uri.path) # Automatic cleanup
end
Hostname verification bypassing creates security vulnerabilities. Applications that disable hostname checking accept certificates for any domain.
# PITFALL: Disabling hostname verification
context.verify_mode = OpenSSL::SSL::VERIFY_NONE # Accepts any certificate
# PITFALL: Partial verification
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
# Missing: context.verify_hostname = true
# SOLUTION: Complete verification
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.verify_hostname = true
# SOLUTION: Custom hostname verification
context.verify_callback = proc do |preverify_ok, store_context|
if preverify_ok
cert = store_context.current_cert
if store_context.error_depth == 0 # Leaf certificate
OpenSSL::SSL.verify_certificate_identity(cert, hostname)
else
true
end
else
false
end
end
Reference
SSL Context Classes
Class | Purpose | Key Methods |
---|---|---|
OpenSSL::SSL::SSLContext |
SSL configuration container | #new , #verify_mode= , #ca_file= |
OpenSSL::SSL::SSLSocket |
Encrypted socket wrapper | #connect , #read , #write , #close |
OpenSSL::SSL::SSLServer |
SSL server socket | #accept , #close |
Verification Modes
Constant | Behavior | Use Case |
---|---|---|
VERIFY_NONE |
No certificate verification | Development, testing |
VERIFY_PEER |
Verify peer certificate | Standard client connections |
VERIFY_FAIL_IF_NO_PEER_CERT |
Require peer certificate | Server requiring client certs |
VERIFY_CLIENT_ONCE |
Verify client cert once per session | Optimized mutual TLS |
Protocol Version Constants
Constant | Protocol | Security Level |
---|---|---|
SSL2_VERSION |
SSL 2.0 | Insecure - deprecated |
SSL3_VERSION |
SSL 3.0 | Insecure - deprecated |
TLS1_VERSION |
TLS 1.0 | Legacy only |
TLS1_1_VERSION |
TLS 1.1 | Legacy only |
TLS1_2_VERSION |
TLS 1.2 | Minimum recommended |
TLS1_3_VERSION |
TLS 1.3 | Preferred modern |
Context Configuration Methods
Method | Parameters | Purpose |
---|---|---|
#cert= |
OpenSSL::X509::Certificate |
Set server/client certificate |
#key= |
OpenSSL::PKey |
Set private key |
#ca_file= |
String (file path) |
Trusted CA certificates |
#ca_path= |
String (directory path) |
CA certificate directory |
#verify_mode= |
Integer (constant) |
Certificate verification behavior |
#verify_hostname= |
Boolean |
Enable hostname verification |
#ciphers= |
String or Array |
Allowed cipher suites |
#min_version= |
Integer (constant) |
Minimum protocol version |
#max_version= |
Integer (constant) |
Maximum protocol version |
SSL Socket Methods
Method | Returns | Description |
---|---|---|
#connect |
self |
Initiate SSL handshake |
#accept |
self |
Accept SSL connection |
#peer_cert |
OpenSSL::X509::Certificate |
Remote peer certificate |
#peer_cert_chain |
Array<Certificate> |
Complete certificate chain |
#cipher |
Array |
Negotiated cipher information |
#ssl_version |
String |
Negotiated protocol version |
#verify_result |
Integer |
Certificate verification status |
#session |
OpenSSL::SSL::Session |
Current SSL session |
Common Exception Classes
Exception | Trigger | Recovery Strategy |
---|---|---|
OpenSSL::SSL::SSLError |
General SSL failures | Check logs, verify configuration |
OpenSSL::X509::CertificateError |
Certificate parsing errors | Validate certificate format |
SocketError |
Network connection issues | Retry with exponential backoff |
Errno::ECONNREFUSED |
Connection refused | Check server status |
Net::ReadTimeout |
Read operation timeout | Increase timeout or retry |
Cipher Suite Format
Component | Example | Purpose |
---|---|---|
Key Exchange | ECDHE , DHE , RSA |
Key agreement method |
Authentication | RSA , ECDSA , PSK |
Identity verification |
Encryption | AES256 , CHACHA20 |
Data encryption algorithm |
MAC | GCM , SHA256 , SHA384 |
Message authentication |
Exclusion | !aNULL , !eNULL , !MD5 |
Disabled algorithms |
Certificate Verification Error Codes
Code | Constant | Meaning |
---|---|---|
2 | V_ERR_UNABLE_TO_GET_ISSUER_CERT |
Missing issuer certificate |
9 | V_ERR_CERT_NOT_YET_VALID |
Certificate not yet valid |
10 | V_ERR_CERT_HAS_EXPIRED |
Certificate has expired |
18 | V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT |
Self-signed certificate |
19 | V_ERR_SELF_SIGNED_CERT_IN_CHAIN |
Self-signed cert in chain |
62 | V_ERR_HOSTNAME_MISMATCH |
Hostname mismatch |