# Ruby and OpenSSL FIPS This document is related to the issue ticket is [Being a co-maintainer of the ruby/openssl for the OpenSSL FIPS mode](https://bugs.ruby-lang.org/issues/19608). See also "What is FIPS". ## How to debug with OpenSSL 3 FIPS Below are the steps to debug Ruby OpenSSL binding (ruby/openssl) with OpenSSL 3 with FIPS enabled. ### Install OpenSSL from the source Build and install OpenSSL from the source with the FIPS option. See [the official configuration options document](https://github.com/openssl/openssl/blob/master/INSTALL.md#configuration-options) for details. * `--libdir=lib`: This option changes the default library directory name "lib64" in x86_64 to "lib". It's useful when you access from the Ruby OpenSSL binding. Because the `mkmf#dir_config` executed by `--with-openssl-dir=<path>` in the later step below expects the "lib" as a default. See [the code](https://github.com/ruby/ruby/blob/v3_2_2/lib/mkmf.rb#L1813). * `enable-fips`: Enabling FIPS * `enable-trace`: Enabling tracing option. You can trace logs in a specific category. See the [example](https://github.com/junaruga/openssl-test/blob/main/trace.c). It's helpful to debug. * `-O0 -g3 -ggdb3 -gdwarf-5`: debugging flags for GCC ``` $ git clone https://github.com/openssl/openssl.git $ cd openssl $ git checkout openssl-3.0.8 $ ./Configure \ --prefix=${HOME}/.local/openssl-3.0.8-fips-debug \ --libdir=lib \ shared \ enable-fips \ enable-trace \ -O0 -g3 -ggdb3 -gdwarf-5 $ make -j$(nproc) $ make install $ LD_LIBRARY_PATH=${HOME}/.local/openssl-3.0.8-fips-debug/lib/ \ ${HOME}/.local/openssl-3.0.8-fips-debug/bin/openssl version -a OpenSSL 3.0.8 7 Feb 2023 (Library: OpenSSL 3.0.8 7 Feb 2023) built on: Tue Apr 25 14:23:34 2023 UTC platform: linux-x86_64 options: bn(64,64) compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -O3 -O0 -g3 -ggdb3 -gdwarf-5 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_BUILDING_OPENSSL -DNDEBUG OPENSSLDIR: "/home/jaruga/.local/openssl-3.0.8-fips-debug/ssl" ENGINESDIR: "/home/jaruga/.local/openssl-3.0.8-fips-debug/lib/engines-3" MODULESDIR: "/home/jaruga/.local/openssl-3.0.8-fips-debug/lib/ossl-modules" Seeding source: os-specific CPUINFO: OPENSSL_ia32cap=0x7ffaf3ffffebffff:0x29c67af ``` ### Test OpenSSL FIPS with a testing program written in C language The program is <https://github.com/junaruga/openssl-test>. You can see how to use the program and test it to check if the used OpenSSL is FIPS or not. The FIPS needs to set the "fips" provider and `default_properties = fips=yes`. Prepare a config file to run OpenSSL with FIPS. ``` $ cat ${HOME}/.local/openssl-3.0.8-fips-debug/ssl/openssl_fips.cnf config_diagnostics = 1 openssl_conf = openssl_init # Need to set the absolute path of the fipsmodule.cnf. # https://github.com/openssl/openssl/issues/17704 .include /home/jaruga/.local/openssl-3.0.8-fips-debug/ssl/fipsmodule.cnf #.include fipsmodule.cnf [openssl_init] providers = provider_sect alg_section = algorithm_sect [provider_sect] fips = fips_sect base = base_sect [base_sect] activate = 1 [algorithm_sect] default_properties = fips=yes ``` Compile the testing program. ``` $ gcc \ -I /home/jaruga/.local/openssl-3.0.8-fips-debug/include \ -L /home/jaruga/.local/openssl-3.0.8-fips-debug/lib \ -lcrypto \ -o fips \ fips.c ``` Run the program. ``` $ LD_LIBRARY_PATH=${HOME}/.local/openssl-3.0.8-fips-debug/lib/ \ OPENSSL_CONF=/home/jaruga/.local/openssl-3.0.8-fips-debug/ssl/openssl_fips.cnf \ ./fips Loaded providers: fips base FIPS enabled: yes ``` ### Debug ruby/openssl with an OpenSSL FIPS #### Build ruby/openssl with an OpenSSL FIPS Install. ``` $ git clone https://github.com/ruby/openssl.git $ cd openssl $ bundle install --standalone ``` Compile with the debug flags. If your OpenSSL's library name is "lib64", you need the `--with-openssl-lib=$HOME/.local/openssl-3.0.8-fips-debug/lib64` option too. The `MAKEFLAGS="V=1"` is to print the compiler (`gcc`) command lines if your Ruby is not configured with `./configure --enable-mkmf-verbose` option. You can also use the `bundle exec rake compile -- --with-verbose` option on this purpose in the Ruby on the master branch. ``` $ MAKEFLAGS="V=1" \ RUBY_OPENSSL_EXTCFLAGS="-O0 -g3 -ggdb3 -gdwarf-5" \ bundle exec rake compile -- \ --enable-debug \ --with-openssl-dir=$HOME/.local/openssl-3.0.8-fips-debug ``` #### Clean the built files Note the `bundle exec rake clean` doesn't do this. ``` $ rm -rf tmp $ rm lib/openssl.so ``` #### Run a program to print the used Ruby and OpenSSL versions ``` $ LD_LIBRARY_PATH=$HOME/.local/openssl-3.0.8-fips-debug/lib/ \ bundle exec rake debug /usr/local/ruby-3.2.1/bin/ruby -I./lib -ropenssl -ve'puts OpenSSL::OPENSSL_VERSION, OpenSSL::OPENSSL_LIBRARY_VERSION' ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux] OpenSSL 3.0.8 7 Feb 2023 OpenSSL 3.0.8 7 Feb 2023 ``` #### Run the unit test You see many failures and errors on the current master branch (`037c181ddf30ed7576eba7d14b95c3742449bf63`) in the ruby/openssl. ``` $ LD_LIBRARY_PATH=$HOME/.local/openssl-3.0.8-fips-debug/lib/ \ OPENSSL_CONF=/home/jaruga/.local/openssl-3.0.8-fips-debug/ssl/openssl_fips.cnf \ bundle exec rake test ... 510 tests, 1943 assertions, 14 failures, 331 errors, 0 pendings, 0 omissions, 0 notifications 32.3529% passed ... ``` #### Debug Let me explain you how to debug with the following tools with an example: reading an encriped .pem file. Create a testing .pem file. ``` $ openssl genrsa -out key.pem 4096 $ ls -l key.pem -rw-------. 1 jaruga jaruga 3272 May 2 19:40 key.pem ``` Run a program to read the .pem file. ``` $ LD_LIBRARY_PATH=$HOME/.local/openssl-3.0.8-fips-debug/lib/ \ OPENSSL_CONF=/home/jaruga/.local/openssl-3.0.8-fips-debug/ssl/openssl_fips.cnf \ ruby -I lib -e "require 'openssl'; OpenSSL::PKey.read(File.read('key.pem'))" ``` ##### GDB Make sure you set the `LD_LIBRARY_PATH` in the GDB prompt to prevent overriding the system OpenSSL that is a depenency of the GDB. ``` $ OPENSSL_CONF=/home/jaruga/.local/openssl-3.0.8-fips-debug/ssl/openssl_fips.cnf \ gdb --args ruby -I lib -e "require 'openssl'; OpenSSL::PKey.read(File.read('key.pem'))" (gdb) set environment LD_LIBRARY_PATH /home/jaruga/.local/openssl-3.0.8-fips-debug/lib ``` ##### ltrace ``` $ LD_LIBRARY_PATH=$HOME/.local/openssl-3.0.8-fips-debug/lib/ \ OPENSSL_CONF=/home/jaruga/.local/openssl-3.0.8-fips-debug/ssl/openssl_fips.cnf \ ltrace -ttt -f -l openssl.so -l libssl.so.3 -l libcrypto.so.3 \ ruby -I lib -e "require 'openssl'; OpenSSL::PKey.read(File.read('key.pem'))" ``` ##### strace ``` $ LD_LIBRARY_PATH=$HOME/.local/openssl-3.0.8-fips-debug/lib OPENSSL_CONF=/home/jaruga/.local/openssl-3.0.8-fips-debug/ssl/openssl_fips.cnf \ strace -f \ ruby -I lib -e "require 'openssl'; OpenSSL::PKey.read(File.read('key.pem'))" ``` ##### SystemTap (stap) Debug with the `stap` command, [SystemTap](https://sourceware.org/systemtap/index.html). [Here](https://hackmd.io/c87t7RNBTgKE8gICpJ70Ww?view) is how to set up SystemTap in Fedora Linux as a reference. Prepare the following bash script. ```bash $ cat test.sh #! /bin/bash LD_LIBRARY_PATH=${HOME}/.local/openssl-3.0.8-fips-debug/lib/ \ OPENSSL_CONF=${HOME}/.local/openssl-3.0.8-fips-debug/ssl/openssl_fips.cnf \ ruby -I lib -e "require 'openssl'; OpenSSL::PKey.read(File.read('key.pem'))" ``` ``` $ chmod +x test.sh $ ./test.sh ``` Run the SystemTap scritpt for the Ruby OpenSSL binding library (`lib/openssl.so`) below. This command only traces all the methods in the .so file. ``` $ stap --example para-callgraph.stp \ 'process("lib/openssl.so").function("*")' \ -c "./test.sh > /dev/null" ``` Another example of the SystemTap script for the OpenSSL library `libcrypto.so`. It takes a long time. ``` $ stap --example para-callgraph.stp \ 'process("/home/jaruga/.local/openssl-3.0.8-fips-debug/lib/libcrypto.so.3").function("*")' \ -c "./test.sh > /dev/null" ```