diff --git a/src/openssl.cr b/src/openssl.cr index 4185518d3982..986cd5d29fc9 100644 --- a/src/openssl.cr +++ b/src/openssl.cr @@ -1,69 +1,99 @@ require "./openssl/lib_ssl" require "./openssl/error" -# NOTE: To use `OpenSSL`, you must explicitly import it with `require "openssl"` +# The OpenSSL module allows for access to Secure Sockets Layer (SSL) and Transport Layer Security (TLS) +# encryption, as well as classes for encrypting data, decrypting data, and computing hashes. It uses +# the SSL library provided by the operating system, which may be either [OpenSSL](https://openssl.org) +# or [LibreSSL](https://www.libressl.org). # -# ## OpenSSL Integration +# WARNING: This module should not be used without first reading the [Security considerations](#security-considerations). # -# - TLS sockets need a context, potentially with keys (required for servers) and configuration. -# - TLS sockets will wrap the underlying TCP socket, and any further communication must happen through the `OpenSSL::SSL::Socket` only. +# To create secure sockets, use either `OpenSSL::SSL::Socket::Client` for client applications, or +# `OpenSSL::SSL::Socket::Server` for servers. These classes use a default context, but you can provide +# your own by supplying an `OpenSSL::SSL::Context`. For more control, consider subclassing `OpenSSL::SSL::Socket`. # -# ## Usage Example +# Hashing algorithms are provided by classes such as `Digest::SHA256` and `Digest::MD5`. If you need +# a different option, you can initialize one using the name of the digest with `OpenSSL::Digest`. +# A Hash-based Message Authentication Code (HMAC) can be computed using `HMAC` and specifying a digest +# `Algorithm`. +# +# The `OpenSSL::Cipher` class can be used for encrypting and decrypting data. +# +# NOTE: To use `OpenSSL`, you must explicitly import it using the `require "openssl"` statement. +# +# ## Security Considerations +# +# Crystal aims to provide reasonable configuration defaults in accordance with +# [Mozilla's recommendations](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29). +# However, these defaults may not be suitable for your application. It is recommended that you refer +# to the Open Worldwide Application Security Project (OWASP) cheat sheet on +# [implementing transport layer protection](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html) +# to ensure the appropriate configuration for your use. # -# Recommended ciphers can be taken from: -# - [OWASP Wiki](https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Cryptographic_Ciphers) -# - [Cipherli.st](https://cipherli.st/) -# - A full list is available at the [OpenSSL Docs](https://www.openssl.org/docs/man1.1.0/apps/ciphers.html#CIPHER-STRINGS) +# If you come across any shortcomings or spots for improvement in Crystal's configuration options, +# please don't hesitate to let us know by [opening an issue](https://github.com/crystal-lang/crystal/issues/new). # -# Do note that: -# - Crystal does its best to provide sane configuration defaults (see [Mozilla-Intermediate](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29)). -# - Linked version of OpenSSL need to be checked for supporting specific protocols and ciphers. -# - If any configurations or choices in Crystal regarding SSL settings and security are found to be lacking or need -# improvement please [open an issue](https://github.com/crystal-lang/crystal/issues/new) and let us know. +# ## Usage Example # # ### Server side # -# NOTE: For the below example to work, a key pair should be attained. +# An `SSL` server is created with a `TCPServer` and a `SSL::Context`. You can then use the +# SSL server like an ordinary TCP server. +# +# NOTE: For the below example to work, a certificate and private key should be attained. # # ``` -# require "socket" # require "openssl" +# require "socket" # -# def server -# # Bind new TCPSocket to port 5555 -# socket = TCPServer.new(5555) +# PORT = ENV["PORT"] ||= "5555" # -# context = OpenSSL::SSL::Context::Server.new -# context.private_key = "/path/to/private.key" -# context.certificate_chain = "/path/to/public.cert" +# # Bind new TCPServer to PORT +# socket = TCPServer.new(PORT.to_i) # -# puts "Server is up" +# context = OpenSSL::SSL::Context::Server.new +# context.private_key = "/path/to/private.key" +# context.certificate_chain = "/path/to/public.cert" # -# socket.accept do |client| -# puts "Got client" +# puts "Server is up. Listening on port #{PORT}." # -# bytes = Bytes.new(20) +# socket.accept do |client| +# puts "Got client" # -# ssl_socket = OpenSSL::SSL::Socket::Server.new(client, context) -# ssl_socket.read(bytes) +# bytes = Bytes.new(20) # -# puts String.new(bytes) +# OpenSSL::SSL::Socket::Server.open(client, context) do |ssl_socket| +# ssl_socket.read(bytes) # end +# +# puts "Client said: #{String.new(bytes)}" # end +# +# socket.close +# puts "Server has stopped." # ``` # # ### Client side # +# An `SSL` client is created with a `TCPSocket` and a `SSL::Context`. Unlike a SSL server, +# a client does not require a certificate or private key. +# +# NOTE: By default, closing an `SSL::Socket` does not close the underlying socket. You need to +# set `SSL::Socket#sync_close=` to true if you want this behaviour. +# # ``` -# require "socket" # require "openssl" +# require "socket" # -# def client -# socket = TCPSocket.new("127.0.0.1", 5555) +# PORT = ENV["PORT"] ||= "5555" +# +# # Bind TCPSocket to PORT and open a connection +# TCPSocket.open("127.0.0.1", PORT) do |socket| # context = OpenSSL::SSL::Context::Client.new # -# ssl_socket = OpenSSL::SSL::Socket::Client.new(socket, context) -# ssl_socket << "Testing" +# OpenSSL::SSL::Socket::Client.open(socket, context) do |ssl_socket| +# ssl_socket << "Hello from client!" +# end # end # ``` module OpenSSL diff --git a/src/openssl/cipher.cr b/src/openssl/cipher.cr index bea9a298fc97..c24c821d54dc 100644 --- a/src/openssl/cipher.cr +++ b/src/openssl/cipher.cr @@ -3,6 +3,10 @@ require "openssl" # A class which can be used to encrypt and decrypt data using a specified cipher. # +# NOTE: The ciphers available to an application are determined by the linked version of the system SSL library. +# A comprehensive list of ciphers can be found in the +# [OpenSSL Cipher documentation](https://www.openssl.org/docs/man3.0/man1/openssl-ciphers.html#CIPHER-STRINGS). +# # ``` # require "random/secure" # diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr index a46e924346a7..5db3e754e79b 100644 --- a/src/openssl/ssl/context.cr +++ b/src/openssl/ssl/context.cr @@ -294,19 +294,31 @@ abstract class OpenSSL::SSL::Context raise OpenSSL::Error.new("SSL_CTX_use_PrivateKey_file") unless ret == 1 end - # Specify a list of TLS ciphers to use or discard. + # Specify a list of TLS ciphers to use or discard for TLSv1.2 and below. # - # This affects only TLSv1.2 and below. See `#security_level=` for some - # sensible system configuration. + # See `#security_level=` for some sensible system configuration. + # + # This method does not impact TLSv1.3 ciphersuites. Use `#cipher_suites=` + # to configure those. + # + # NOTE: The ciphers available to an application are determined by the + # linked version of the system SSL library. A comprehensive list + # of ciphers can be found in the + # [OpenSSL Cipher documentation](https://www.openssl.org/docs/man3.0/man1/openssl-ciphers.html#CIPHER-STRINGS). def ciphers=(ciphers : String) ret = LibSSL.ssl_ctx_set_cipher_list(@handle, ciphers) raise OpenSSL::Error.new("SSL_CTX_set_cipher_list") if ret == 0 ciphers end - # Specify a list of TLS cipher suites to use or discard. + # Specify a list of TLS ciphersuites to use or discard for TLSv1.3. # # See `#security_level=` for some sensible system configuration. + # + # NOTE: The ciphersuites available to an application are determined by the + # linked version of the system SSL library. A comprehensive list + # of ciphersuites can be found in the + # [OpenSSL Cipher documentation](https://www.openssl.org/docs/man3.0/man1/openssl-ciphers.html#TLS-v1.3-cipher-suites). def cipher_suites=(cipher_suites : String) {% if LibSSL.has_method?(:ssl_ctx_set_ciphersuites) %} ret = LibSSL.ssl_ctx_set_ciphersuites(@handle, cipher_suites)