CrackedRuby CrackedRuby

Overview

TLS (Transport Layer Security) and its predecessor SSL (Secure Sockets Layer) establish encrypted communication channels between network endpoints. The protocol operates at the transport layer, sitting between TCP and application protocols like HTTP, SMTP, or FTP. TLS 1.3, standardized in RFC 8446, represents the current iteration after deprecating SSL 2.0, SSL 3.0, TLS 1.0, and TLS 1.1 due to security vulnerabilities.

The protocol addresses three core security requirements: confidentiality through symmetric encryption, integrity through message authentication codes, and authentication through public key infrastructure. When a client connects to a server, both parties negotiate protocol versions, exchange certificates, verify identities, and establish shared encryption keys before transmitting application data.

Ruby accesses TLS functionality through the OpenSSL library, which wraps the underlying C library. The standard library includes openssl, providing classes for SSL contexts, sockets, certificates, and cryptographic operations. Third-party gems extend this foundation with higher-level abstractions for common use cases.

require 'openssl'
require 'socket'

# Basic TLS server setup
tcp_server = TCPServer.new(443)
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.cert = OpenSSL::X509::Certificate.new(File.read('cert.pem'))
ssl_context.key = OpenSSL::PKey::RSA.new(File.read('key.pem'))

ssl_server = OpenSSL::SSL::SSLServer.new(tcp_server, ssl_context)
# => #<OpenSSL::SSL::SSLServer:0x00007f8b>

Key Principles

TLS operates through a multi-phase handshake protocol that establishes secure parameters before application data flows. The handshake begins with the client sending a ClientHello message containing supported protocol versions, cipher suites, compression methods, and random data. The server responds with ServerHello, selecting the highest mutually supported protocol version and a cipher suite from the client's list.

Certificate exchange follows the initial negotiation. The server presents its X.509 certificate, which contains the server's public key and identity information signed by a Certificate Authority (CA). The client validates this certificate against its trusted CA store, checking the signature chain, validity dates, and hostname matching. For mutual TLS, the server may also request a client certificate for bidirectional authentication.

Key exchange mechanisms vary by cipher suite. RSA-based suites encrypt a pre-master secret with the server's public key from its certificate. Ephemeral Diffie-Hellman (DHE) and Elliptic Curve Diffie-Hellman Ephemeral (ECDHE) provide forward secrecy by generating temporary keys for each session. These temporary keys ensure that compromising long-term private keys does not decrypt past sessions.

After key exchange, both parties derive session keys from the pre-master secret using a pseudorandom function. These symmetric keys encrypt and authenticate subsequent messages. The handshake concludes with Finished messages that verify all handshake data using the newly established keys.

Cipher suites specify the algorithms for each security function. A suite like TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 indicates:

  • ECDHE for key exchange
  • RSA for authentication
  • AES-256-GCM for symmetric encryption
  • SHA-384 for message authentication

TLS 1.3 simplified this structure by removing RSA key exchange, static Diffie-Hellman, and all non-AEAD cipher suites. The protocol now mandates forward secrecy and uses a more efficient handshake with fewer round trips.

Record Protocol handles the actual data transmission. Each record contains a content type, protocol version, length, and payload. The payload undergoes compression (now discouraged), MAC computation or AEAD authentication, and encryption before transmission. The receiving end performs these operations in reverse, verifying integrity before decryption.

Session resumption mechanisms reduce handshake overhead for repeated connections. Session IDs allow clients to reference previously negotiated parameters, while session tickets encrypt session data with a server key. TLS 1.3 introduced 0-RTT (zero round-trip time) resumption, enabling immediate application data transmission on reconnection.

Ruby Implementation

Ruby's OpenSSL binding provides OpenSSL::SSL::SSLContext for configuring TLS parameters. This object holds certificates, private keys, cipher suite preferences, and verification callbacks. Each SSLContext instance represents a distinct security policy that multiple connections can share.

require 'openssl'

# Configure server context
context = OpenSSL::SSL::SSLContext.new
context.min_version = OpenSSL::SSL::TLS1_2_VERSION
context.max_version = OpenSSL::SSL::TLS1_3_VERSION
context.ciphers = 'ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM'

# Load certificate chain
context.cert = OpenSSL::X509::Certificate.new(File.read('server.crt'))
context.extra_chain_cert = [
  OpenSSL::X509::Certificate.new(File.read('intermediate.crt'))
]
context.key = OpenSSL::PKey::RSA.new(File.read('server.key'))

# Configure verification
context.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
context.ca_file = '/etc/ssl/certs/ca-bundle.crt'
# => #<OpenSSL::SSL::SSLContext>

The OpenSSL::SSL::SSLSocket wraps a TCP socket with TLS encryption. Creating an SSL socket requires an existing TCP socket and an SSLContext. The connect or accept method initiates the handshake, blocking until completion or timeout.

require 'socket'

# Client connection
tcp_socket = TCPSocket.new('example.com', 443)
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.sync_close = true  # Close underlying socket with SSL socket
ssl_socket.hostname = 'example.com'  # For SNI
ssl_socket.connect
# => #<OpenSSL::SSL::SSLSocket>

# Access connection details
ssl_socket.cipher
# => ["ECDHE-RSA-AES256-GCM-SHA384", "TLSv1.2", 256, 256]

ssl_socket.peer_cert
# => #<OpenSSL::X509::Certificate>

Certificate verification requires careful configuration. The verify_mode attribute controls whether verification occurs and how failures are handled. Setting VERIFY_PEER enables verification but allows connections to proceed even on failure unless combined with VERIFY_FAIL_IF_NO_PEER_CERT.

# Custom verification callback
context.verify_callback = proc do |preverify_ok, store_context|
  if preverify_ok
    cert = store_context.current_cert
    # Additional validation logic
    if cert.subject.to_s.include?('CN=*.example.com')
      # Check if hostname matches
      hostname = store_context.chain[0].subject.to_s[/CN=([^,]+)/, 1]
      hostname.end_with?('.example.com')
    else
      preverify_ok
    end
  else
    # Log verification errors
    puts "Verification error: #{store_context.error_string}"
    false
  end
end

SNI (Server Name Indication) allows hosting multiple TLS sites on a single IP address. The client includes the requested hostname in the ClientHello, enabling the server to present the correct certificate. Ruby supports SNI through the hostname attribute on client sockets and servername callbacks on servers.

# Server-side SNI handling
context.servername_cb = proc do |socket, hostname|
  case hostname
  when 'api.example.com'
    api_context
  when 'www.example.com'
    www_context
  else
    nil  # Use default context
  end
end

Session management improves performance for repeated connections. Ruby maintains a session cache automatically, but applications can control cache behavior through SSLContext configuration.

# Session caching
context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
context.session_cache_size = 128
context.timeout = 300  # Session lifetime in seconds

# Export session for external storage
session = ssl_socket.session
session_data = session.to_der
# Store session_data in Redis, memcached, etc.

# Resume from stored session
new_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
new_socket.session = OpenSSL::SSL::Session.new(session_data)
new_socket.connect

ALPN (Application-Layer Protocol Negotiation) enables protocol selection during the TLS handshake. HTTP/2 uses ALPN to negotiate the upgrade from HTTP/1.1, avoiding an additional round trip.

# Client ALPN
context.alpn_protocols = ['h2', 'http/1.1']

# Server ALPN
context.alpn_select_cb = proc do |protocols|
  if protocols.include?('h2')
    'h2'
  else
    'http/1.1'
  end
end

# After handshake
ssl_socket.alpn_protocol
# => "h2"

Practical Examples

Web servers handle TLS connections through libraries that abstract the OpenSSL details. Puma and Unicorn both support TLS configuration through command-line options or configuration files.

# Puma TLS configuration
# config/puma.rb
ssl_bind '0.0.0.0', '443', {
  key: '/path/to/server.key',
  cert: '/path/to/server.crt',
  verify_mode: 'peer',
  ca: '/path/to/ca-bundle.crt',
  ssl_cipher_filter: 'ECDHE+AESGCM:ECDHE+CHACHA20'
}

HTTP clients like Net::HTTP wrap TLS configuration in a high-level API. The library handles certificate verification by default, though applications can customize this behavior.

require 'net/http'
require 'uri'

uri = URI('https://api.example.com/data')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

# Certificate verification
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.ca_file = '/etc/ssl/certs/ca-bundle.crt'

# Client certificate authentication
http.cert = OpenSSL::X509::Certificate.new(File.read('client.crt'))
http.key = OpenSSL::PKey::RSA.new(File.read('client.key'))

# Custom verification
http.verify_callback = proc do |preverify_ok, store_context|
  preverify_ok && validate_certificate_pins(store_context.current_cert)
end

response = http.get(uri.path)
# => #<Net::HTTPOK 200 OK>

Database connections frequently require TLS for transmitting credentials and sensitive data. PostgreSQL clients configure TLS through connection parameters or environment variables.

require 'pg'

# PostgreSQL TLS connection
conn = PG.connect(
  host: 'db.example.com',
  sslmode: 'verify-full',
  sslcert: 'client.crt',
  sslkey: 'client.key',
  sslrootcert: 'ca.crt'
)

# Verify TLS status
conn.exec("SHOW ssl").getvalue(0, 0)
# => "on"

Redis TLS support varies by client library. The redis gem supports TLS through the URL scheme and additional SSL parameters.

require 'redis'

redis = Redis.new(
  url: 'rediss://redis.example.com:6380',
  ssl_params: {
    cert: OpenSSL::X509::Certificate.new(File.read('client.crt')),
    key: OpenSSL::PKey::RSA.new(File.read('client.key')),
    ca_file: '/etc/ssl/certs/ca-bundle.crt',
    verify_mode: OpenSSL::SSL::VERIFY_PEER
  }
)

SMTP and IMAP protocols support TLS through STARTTLS commands that upgrade plaintext connections or through implicit TLS on dedicated ports.

require 'net/smtp'

smtp = Net::SMTP.new('smtp.example.com', 587)
smtp.enable_starttls_auto

# Configure TLS context
smtp.open_timeout = 10
smtp.read_timeout = 10

smtp.start('localhost', 'user', 'pass', :login) do |smtp_session|
  smtp_session.send_message(
    message_text,
    'from@example.com',
    ['to@example.com']
  )
end

Security Implications

Certificate validation represents the most critical security check in TLS. Applications must verify that the server certificate chains to a trusted CA and that the certificate's Common Name or Subject Alternative Names match the requested hostname. Disabling verification or ignoring errors defeats TLS security.

# INSECURE - Never use in production
context.verify_mode = OpenSSL::SSL::VERIFY_NONE

# SECURE - Proper verification
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.ca_file = '/etc/ssl/certs/ca-bundle.crt'

# Additional hostname verification
def verify_hostname(certificate, hostname)
  san_names = certificate.extensions.find do |ext|
    ext.oid == 'subjectAltName'
  end&.value&.split(',')&.map { |s| s.strip.sub(/^DNS:/, '') } || []
  
  cn_name = certificate.subject.to_a.find { |attr| attr[0] == 'CN' }&.at(1)
  
  names = san_names.empty? ? [cn_name] : san_names
  names.any? { |name| match_hostname(name, hostname) }
end

Forward secrecy requires ephemeral key exchange algorithms. Cipher suites using static RSA or DH key exchange allow attackers who compromise the server's private key to decrypt past sessions. Modern configurations exclude these suites.

# Good cipher suite selection
context.ciphers = 'ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:!RSA:!aNULL:!eNULL'

# Verify negotiated suite provides forward secrecy
cipher_name = ssl_socket.cipher[0]
has_forward_secrecy = cipher_name.start_with?('ECDHE-', 'DHE-')

Protocol version selection impacts security significantly. TLS 1.0 and 1.1 have known vulnerabilities and should be disabled. TLS 1.2 remains acceptable with careful cipher suite selection, while TLS 1.3 provides the strongest security.

# Enforce minimum TLS version
context.min_version = OpenSSL::SSL::TLS1_2_VERSION
context.max_version = nil  # No maximum restriction

# Reject deprecated protocols
context.options = OpenSSL::SSL::OP_NO_SSLv2 |
                  OpenSSL::SSL::OP_NO_SSLv3 |
                  OpenSSL::SSL::OP_NO_TLSv1 |
                  OpenSSL::SSL::OP_NO_TLSv1_1

Certificate pinning provides additional security by validating that the server presents a specific certificate or one signed by a specific CA. This prevents attacks using fraudulent certificates issued by compromised CAs.

EXPECTED_FINGERPRINTS = [
  'AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD'
].freeze

context.verify_callback = proc do |preverify_ok, store_context|
  if preverify_ok && store_context.chain.size == 1
    cert = store_context.current_cert
    fingerprint = OpenSSL::Digest::SHA256.hexdigest(cert.to_der)
    EXPECTED_FINGERPRINTS.include?(fingerprint.scan(/../).join(':').upcase)
  else
    preverify_ok
  end
end

Renegotiation attacks exploited a TLS 1.0-1.2 flaw that allowed injecting data into established connections. Secure renegotiation extensions addressed this, but disabling renegotiation entirely provides stronger protection.

# Disable renegotiation
context.options |= OpenSSL::SSL::OP_NO_RENEGOTIATION if defined?(OpenSSL::SSL::OP_NO_RENEGOTIATION)

Compression attacks like CRIME exploit TLS compression to extract secrets through timing analysis. Modern TLS implementations disable compression by default.

# Ensure compression is disabled
context.options |= OpenSSL::SSL::OP_NO_COMPRESSION

Common Pitfalls

Hostname verification failures occur when developers manually construct SSL sockets without setting the hostname attribute. The OpenSSL library does not automatically verify that the certificate's CN or SAN matches the requested hostname unless explicitly configured.

# WRONG - Missing hostname verification
tcp_socket = TCPSocket.new('example.com', 443)
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.connect

# CORRECT - Explicit hostname verification
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.hostname = 'example.com'  # Enables SNI and hostname verification
ssl_socket.connect

Certificate chain issues arise when servers fail to send intermediate certificates. Clients may have the root CA but lack the intermediate certificates needed to validate the chain. Servers must configure extra_chain_cert to include all intermediates.

# Server configuration with full chain
context.cert = OpenSSL::X509::Certificate.new(File.read('server.crt'))
context.extra_chain_cert = [
  OpenSSL::X509::Certificate.new(File.read('intermediate.crt')),
  # Add all intermediates, but not the root
]

Timeout handling requires setting appropriate values on both TCP and SSL layers. Applications that fail to configure timeouts may hang indefinitely on network issues or slow handshakes.

require 'timeout'

tcp_socket = TCPSocket.new('example.com', 443)
tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, [10, 0].pack('l_2'))
tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, [10, 0].pack('l_2'))

ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
Timeout.timeout(10) do
  ssl_socket.connect
end

Self-signed certificate handling tempts developers to disable verification entirely. The correct approach adds the self-signed certificate to the trusted CA store or implements custom verification logic.

# WRONG - Disables all verification
context.verify_mode = OpenSSL::SSL::VERIFY_NONE

# CORRECT - Trust specific self-signed certificate
store = OpenSSL::X509::Store.new
store.add_cert(OpenSSL::X509::Certificate.new(File.read('self-signed.crt')))
context.cert_store = store
context.verify_mode = OpenSSL::SSL::VERIFY_PEER

Protocol downgrade attacks exploit fallback mechanisms that retry connections with older protocols. Modern implementations should fail rather than downgrade when servers reject the latest protocol versions.

# WRONG - Allows downgrade
begin
  connect_with_tls_1_3
rescue OpenSSL::SSL::SSLError
  connect_with_tls_1_2
end

# CORRECT - Fail on protocol mismatch
context.min_version = OpenSSL::SSL::TLS1_2_VERSION
ssl_socket.connect  # Raises if server doesn't support TLS 1.2+

Buffer and memory management affects long-lived connections. SSL sockets maintain internal buffers that can accumulate data if not properly drained. Applications must read all available data before closing connections.

# Drain read buffer before closing
until ssl_socket.eof?
  begin
    ssl_socket.read_nonblock(4096)
  rescue IO::WaitReadable
    break
  end
end
ssl_socket.close

Tools & Ecosystem

The openssl gem ships with Ruby and provides comprehensive TLS functionality. Ruby 2.4+ includes significant improvements to the OpenSSL binding, including support for modern cipher suites and TLS 1.3.

OpenSSL command-line tools diagnose TLS issues and inspect certificates. The s_client command connects to servers and displays handshake details.

# Test TLS connection
openssl s_client -connect example.com:443 -servername example.com

# Display certificate details
openssl x509 -in cert.pem -text -noout

# Test specific cipher
openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES256-GCM-SHA384

# Verify certificate chain
openssl verify -CAfile ca-bundle.crt server.crt

The certified gem simplifies certificate validation by ensuring Net::HTTP verifies certificates properly. It addresses common pitfalls by enforcing verification and providing better error messages.

require 'certified'
require 'net/http'

# Automatic CA bundle location
Net::HTTP.get(URI('https://example.com'))

The http gem provides a modern HTTP client with sensible TLS defaults. It verifies certificates by default and offers a cleaner API than Net::HTTP.

require 'http'

response = HTTP.get('https://example.com')

# Custom SSL context
response = HTTP.use(ssl_context: custom_context).get('https://example.com')

Faraday abstracts HTTP client libraries behind a unified interface. TLS configuration varies by adapter, but Faraday provides middleware for common scenarios.

require 'faraday'

conn = Faraday.new(url: 'https://example.com') do |f|
  f.ssl.verify = true
  f.ssl.ca_file = '/etc/ssl/certs/ca-bundle.crt'
  f.ssl.client_cert = OpenSSL::X509::Certificate.new(File.read('client.crt'))
  f.ssl.client_key = OpenSSL::PKey::RSA.new(File.read('client.key'))
  f.adapter Faraday.default_adapter
end

Let's Encrypt provides free, automated certificate issuance through the ACME protocol. The acme-client gem implements the protocol for obtaining and renewing certificates.

require 'acme-client'

client = Acme::Client.new(
  private_key: OpenSSL::PKey::RSA.new(4096),
  directory: 'https://acme-v02.api.letsencrypt.org/directory'
)

account = client.new_account(
  contact: 'mailto:admin@example.com',
  terms_of_service_agreed: true
)

order = client.new_order(identifiers: ['example.com'])
authorization = order.authorizations.first

challenge = authorization.http
# Deploy challenge.file_content to http://example.com/.well-known/acme-challenge/#{challenge.token}

challenge.request_validation
sleep(2) while challenge.status == 'pending'

csr = Acme::Client::CertificateRequest.new(
  private_key: OpenSSL::PKey::RSA.new(4096),
  subject: { common_name: 'example.com' }
)

order.finalize(csr: csr)
sleep(2) while order.status == 'processing'

certificate = order.certificate
File.write('cert.pem', certificate)

The sslyze and testssl.sh tools audit TLS configurations, identifying weak cipher suites, protocol vulnerabilities, and certificate issues. These tools complement application testing by validating server configurations.

Reference

Protocol Versions

Version RFC Status Notes
SSL 2.0 N/A Deprecated Removed from all implementations
SSL 3.0 6101 Deprecated Vulnerable to POODLE attack
TLS 1.0 2246 Deprecated Vulnerable to BEAST, requires careful configuration
TLS 1.1 4346 Deprecated Limited improvements over TLS 1.0
TLS 1.2 5246 Current Secure with proper cipher suite selection
TLS 1.3 8446 Current Recommended, simplified handshake and improved security

Common Cipher Suites

Cipher Suite Key Exchange Authentication Encryption MAC Forward Secrecy
ECDHE-RSA-AES256-GCM-SHA384 ECDHE RSA AES-256-GCM AEAD Yes
ECDHE-RSA-AES128-GCM-SHA256 ECDHE RSA AES-128-GCM AEAD Yes
ECDHE-RSA-CHACHA20-POLY1305 ECDHE RSA ChaCha20 Poly1305 Yes
DHE-RSA-AES256-GCM-SHA384 DHE RSA AES-256-GCM AEAD Yes
AES256-GCM-SHA384 RSA RSA AES-256-GCM AEAD No
AES128-GCM-SHA256 RSA RSA AES-128-GCM AEAD No

SSLContext Configuration

Attribute Type Description
cert X509::Certificate Server or client certificate
key PKey Private key for certificate
extra_chain_cert Array Intermediate certificates
ca_file String Path to CA bundle
ca_path String Directory containing CA certificates
verify_mode Integer Certificate verification behavior
verify_depth Integer Maximum chain depth to verify
verify_callback Proc Custom verification logic
ciphers String Cipher suite preference list
min_version Integer Minimum acceptable protocol version
max_version Integer Maximum acceptable protocol version
options Integer Protocol and security options bitmask
servername_cb Proc SNI callback for server contexts
session_cache_mode Integer Session cache behavior
timeout Integer Session timeout in seconds

Verification Modes

Constant Behavior
VERIFY_NONE No verification performed
VERIFY_PEER Verify peer certificate
VERIFY_FAIL_IF_NO_PEER_CERT Require peer certificate in server mode
VERIFY_CLIENT_ONCE Request certificate only once in renegotiation

Common Error Codes

Error Cause Solution
certificate verify failed Certificate chain validation failed Check ca_file, verify certificate chain complete
SSL_connect returned=1 errno=0 Handshake protocol error Verify protocol versions compatible
wrong version number Protocol version mismatch Check min_version and max_version settings
certificate has expired Certificate validity period ended Renew certificate
self signed certificate Certificate not signed by trusted CA Add certificate to ca_file or cert_store
unable to get local issuer certificate Missing intermediate or root certificate Add missing certificates to chain
hostname does not match certificate SNI or hostname verification failed Verify hostname attribute set correctly

OpenSSL Version Constants

Constant Value
SSL2_VERSION 0x0002
SSL3_VERSION 0x0300
TLS1_VERSION 0x0301
TLS1_1_VERSION 0x0302
TLS1_2_VERSION 0x0303
TLS1_3_VERSION 0x0304

Session Cache Modes

Constant Description
SESSION_CACHE_OFF Disable session caching
SESSION_CACHE_CLIENT Enable client-side caching
SESSION_CACHE_SERVER Enable server-side caching
SESSION_CACHE_BOTH Enable both client and server caching
SESSION_CACHE_NO_AUTO_CLEAR Disable automatic cache cleanup