From 3142d18a06dbc7ad02e6228d3d446f9b0eb947d4 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 16 Mar 2023 17:19:19 +0100 Subject: [PATCH 1/2] CI: Add OpenSSL FIPS mode case. test/openssl/fixtures/ssl/openssl_fips.cnf.tmpl: I referred to the following document for the openssl config file for FIPS mode. - Making all applications use the FIPS module by default It seems that the `.include` syntax only requires the absolute path. So, the placeholder OPENSSL_DIR in the template file is replaced with the actual OpenSSL directory. .github/workflows/test.yml: The `TEST_RUBY_OPENSSL_FIPS_ENABLED` environment variable is set in the FIPS mode CI case. It can be used in the unit tests. --- .github/workflows/test.yml | 34 +++++++++++++++++-- .../fixtures/ssl/openssl_fips.cnf.tmpl | 19 +++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 test/openssl/fixtures/ssl/openssl_fips.cnf.tmpl diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2813cf6f..374447f87 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,7 +51,7 @@ jobs: test-openssls: name: >- - ${{ matrix.openssl }} + ${{ matrix.openssl }} ${{ matrix.name_extra || '' }} runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -70,6 +70,9 @@ jobs: - libressl-3.5.3 - libressl-3.6.1 - libressl-3.7.0 # Development release + fips_enabled: [ false ] + include: + - { os: ubuntu-latest, ruby: "3.0", openssl: openssl-3.0.8, fips_enabled: true, append_configure: 'enable-fips', name_extra: 'fips' } steps: - name: repo checkout uses: actions/checkout@v3 @@ -83,7 +86,7 @@ jobs: tar xf ${{ matrix.openssl }}.tar.gz && cd ${{ matrix.openssl }} # shared is required for 1.0.x. ./Configure --prefix=$HOME/.openssl/${{ matrix.openssl }} --libdir=lib \ - shared linux-x86_64 + shared linux-x86_64 ${{ matrix.append_configure }} make depend ;; libressl-*) @@ -98,6 +101,26 @@ jobs: make -j4 make install_sw + - name: prepare openssl fips + run: make install_fips + working-directory: tmp/build-openssl/${{ matrix.openssl }} + if: matrix.fips_enabled + + - name: set the open installed directory + run: > + sed -e "s|OPENSSL_DIR|$HOME/.openssl/${{ matrix.openssl }}|" + test/openssl/fixtures/ssl/openssl_fips.cnf.tmpl > + test/openssl/fixtures/ssl/openssl_fips.cnf + if: matrix.fips_enabled + + - name: set openssl config file path for fips. + run: echo "OPENSSL_CONF=$(pwd)/test/openssl/fixtures/ssl/openssl_fips.cnf" >> $GITHUB_ENV + if: matrix.fips_enabled + + - name: set fips enviornment variable for testing. + run: echo "TEST_RUBY_OPENSSL_FIPS_ENABLED=true" >> $GITHUB_ENV + if: matrix.fips_enabled + - name: load ruby uses: ruby/setup-ruby@v1 with: @@ -112,3 +135,10 @@ jobs: - name: test run: rake test TESTOPTS="-v --no-show-detail-immediately" OSSL_MDEBUG=1 timeout-minutes: 5 + if: ${{ !matrix.fips_enabled }} + + # Run only the passing tests on the FIPS mode as a temporary workaround. + # TODO Fix other tests, and run all the tests on FIPS mode. + - name: test on fips mode + run: ruby -Ilib test/openssl/test_fips.rb + if: matrix.fips_enabled diff --git a/test/openssl/fixtures/ssl/openssl_fips.cnf.tmpl b/test/openssl/fixtures/ssl/openssl_fips.cnf.tmpl new file mode 100644 index 000000000..be0768d52 --- /dev/null +++ b/test/openssl/fixtures/ssl/openssl_fips.cnf.tmpl @@ -0,0 +1,19 @@ +config_diagnostics = 1 +openssl_conf = openssl_init + +# It seems that the .include needs an absolute path. +.include OPENSSL_DIR/ssl/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 From 285baec71c1b1ea02479775313cc824da85b0f10 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 16 Mar 2023 21:36:43 +0100 Subject: [PATCH 2/2] Implement FIPS functions on OpenSSL 3. This commit is to implement the `OpenSSL::OPENSSL_FIPS`, `ossl_fips_mode_get` and `ossl_fips_mode_set` to pass the test `test/openssl/test_fips.rb`. It seems that the `OPENSSL_FIPS` macro is not used on the FIPS mode case any more, and some FIPS related APIs also were removed in OpenSSL 3. See the document the section OPENSSL 3.0 > Main Changes from OpenSSL 1.1.1 > Other notable deprecations and changes - Removed FIPS_mode() and FIPS_mode_set() . The `OpenSSL::OPENSSL_FIPS` returns always true in OpenSSL 3 because the used functions `EVP_default_properties_enable_fips` and `EVP_default_properties_is_fips_enabled` works with the OpenSSL installed without FIPS option. The `TEST_RUBY_OPENSSL_FIPS_ENABLED` is set on the FIPS mode case on the CI. Because I want to test that the `OpenSSL.fips_mode` returns the `true` or 'false' surely in the CI. You can test the FIPS mode case by setting `TEST_RUBY_OPENSSL_FIPS_ENABLED` on local too. Right now I don't find a better way to get the status of the FIPS mode enabled or disabled for this purpose. I am afraid of the possibility that the FIPS test case is unintentionally skipped. I also replaced the ambiguous "returns" with "should return" in the tests. --- ext/openssl/ossl.c | 25 +++++++++++++++++++++---- test/openssl/test_fips.rb | 32 ++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 71ddcb9f0..be97b97a1 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -418,7 +418,11 @@ static VALUE ossl_fips_mode_get(VALUE self) { -#ifdef OPENSSL_FIPS +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + VALUE enabled; + enabled = EVP_default_properties_is_fips_enabled(NULL) ? Qtrue : Qfalse; + return enabled; +#elif OPENSSL_FIPS VALUE enabled; enabled = FIPS_mode() ? Qtrue : Qfalse; return enabled; @@ -442,8 +446,18 @@ ossl_fips_mode_get(VALUE self) static VALUE ossl_fips_mode_set(VALUE self, VALUE enabled) { - -#ifdef OPENSSL_FIPS +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + if (RTEST(enabled)) { + if (!EVP_default_properties_enable_fips(NULL, 1)) { + ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + } + } else { + if (!EVP_default_properties_enable_fips(NULL, 0)) { + ossl_raise(eOSSLError, "Turning off FIPS mode failed"); + } + } + return enabled; +#elif OPENSSL_FIPS if (RTEST(enabled)) { int mode = FIPS_mode(); if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ @@ -1198,7 +1212,10 @@ Init_openssl(void) * Boolean indicating whether OpenSSL is FIPS-capable or not */ rb_define_const(mOSSL, "OPENSSL_FIPS", -#ifdef OPENSSL_FIPS +/* OpenSSL 3 is FIPS-capable even when it is installed without fips option */ +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + Qtrue +#elif OPENSSL_FIPS Qtrue #else Qfalse diff --git a/test/openssl/test_fips.rb b/test/openssl/test_fips.rb index 03f7761a0..dfc1729b3 100644 --- a/test/openssl/test_fips.rb +++ b/test/openssl/test_fips.rb @@ -4,20 +4,44 @@ if defined?(OpenSSL) class OpenSSL::TestFIPS < OpenSSL::TestCase + def test_fips_mode_get_is_true_on_fips_mode_enabled + unless ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"] + omit "Only for FIPS mode environment" + end + + assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;") + assert OpenSSL.fips_mode == true, ".fips_mode should return true on FIPS mode enabled" + end; + end + + def test_fips_mode_get_is_false_on_fips_mode_disabled + if ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"] + omit "Only for non-FIPS mode environment" + end + + assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;") + message = ".fips_mode should return false on FIPS mode disabled. " \ + "If you run the test on FIPS mode, please set " \ + "TEST_RUBY_OPENSSL_FIPS_ENABLED=true" + assert OpenSSL.fips_mode == false, message + end; + end + def test_fips_mode_is_reentrant OpenSSL.fips_mode = false OpenSSL.fips_mode = false end - def test_fips_mode_get - return unless OpenSSL::OPENSSL_FIPS + def test_fips_mode_get_with_fips_mode_set + omit('OpenSSL is not FIPS-capable') unless OpenSSL::OPENSSL_FIPS + assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;") begin OpenSSL.fips_mode = true - assert OpenSSL.fips_mode == true, ".fips_mode returns true when .fips_mode=true" + assert OpenSSL.fips_mode == true, ".fips_mode should return true when .fips_mode=true" OpenSSL.fips_mode = false - assert OpenSSL.fips_mode == false, ".fips_mode returns false when .fips_mode=false" + assert OpenSSL.fips_mode == false, ".fips_mode should return false when .fips_mode=false" rescue OpenSSL::OpenSSLError pend "Could not set FIPS mode (OpenSSL::OpenSSLError: \#$!); skipping" end