# 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"
```