CrackedRuby logo

CrackedRuby

SSL/TLS

Secure Socket Layer and Transport Layer Security implementation in Ruby using OpenSSL bindings for encrypted network communication.

Standard Library Security and Cryptography
4.5.6

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