Skip to content

Commit

Permalink
add FIPS module verification mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
gegles committed Jul 30, 2024
1 parent 274288c commit 3800327
Showing 1 changed file with 83 additions and 35 deletions.
118 changes: 83 additions & 35 deletions recipes/openssl/3.x.x/conanfile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import re
from io import StringIO

from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.errors import ConanException, ConanInvalidConfiguration
from conan.tools.apple import fix_apple_shared_install_name, is_apple_os, XCRun
from conan.tools.build import build_jobs
from conan.tools.build import build_jobs, can_run
from conan.tools.files import chdir, copy, get, replace_in_file, rm, rmdir, save
from conan.tools.gnu import AutotoolsToolchain
from conan.tools.layout import basic_layout
Expand Down Expand Up @@ -115,27 +118,30 @@ def _settings_build(self):
return getattr(self, "settings_build", self.settings)

@property
def _fips_validated_version(self):
# As of version 3.3.1, the FIPS module is validated for the following versions
# see https://openssl-library.org/source/ (excluding ancient 3.0.0)
versions = ['3.0.8', '3.0.9']
versions = sorted([Version(v) for v in versions], reverse=True)

# Find the closest version that is less than or equal to the current version
fips_validated_version = next((v for v in versions if v <= Version(self.version)), None)
return fips_validated_version
def _fips_version(self):
if self.options.use_validated_fips:
# As of version 3.3.1, the FIPS module is validated for the following versions
# see https://openssl-library.org/source/ (excluding ancient 3.0.0)
versions = ['3.0.8', '3.0.9']
versions = sorted([Version(v) for v in versions], reverse=True)

# Find the closest version that is less than or equal to the current version
fips_validated_version = next((v for v in versions if v <= Version(self.version)), None)
return fips_validated_version
else:
return self.version

@property
def _is_fips_enabled(self):
return not self.options.no_fips or self.options.use_validated_fips

@property
def _is_fips_validated(self):
return self.version == self._fips_validated_version
def _provides_validated_fips(self):
return self.options.use_validated_fips and self.version == self._fips_version

@property
def _fips_provider_dir(self):
if self.options.use_validated_fips and not self._is_fips_validated:
if self.options.use_validated_fips and not self._provides_validated_fips:
return self.dependencies["openssl"].runenv_info.vars(self)["OPENSSL_MODULES"]
elif not self.options.no_fips:
return os.path.join(self.source_folder, "providers")
Expand Down Expand Up @@ -167,14 +173,12 @@ def requirements(self):
if not self.options.no_zlib:
self.requires("zlib/[>=1.2.11 <2]")

if self.options.use_validated_fips and self._fips_validated_version:
fips_version = self._fips_validated_version
self_validated = self._is_fips_validated
if not self_validated:
self.output.info(f"Using validated FIPS module from openssl/{fips_version}")
self.requires(f"openssl/{fips_version}", visible=False, libs=False, headers=False, run=False, options={'no_fips': False})
if self.options.use_validated_fips:
if not self._provides_validated_fips:
self.output.info(f"Using validated FIPS module from openssl/{self._fips_version}")
self.requires(f"openssl/{self._fips_version}", visible=False, libs=False, headers=False, run=False, options={'no_fips': False})
else:
self.output.info(f"Using validated FIPS module from self (i.e. {fips_version})")
self.output.info(f"Using validated FIPS module from self (i.e. {self._fips_version})")

def validate(self):
if self.settings.os == "Emscripten":
Expand All @@ -185,13 +189,12 @@ def validate(self):
raise ConanInvalidConfiguration("OpenSSL 3 does not support building shared libraries for iOS")

if self.options.use_validated_fips:
fips_version = self._fips_validated_version
if fips_version is None:
if self._fips_version is None:
raise ConanInvalidConfiguration(f"OpenSSL {self.version} - no compatible FIPS validated version found")
if self.options.no_fips:
raise ConanInvalidConfiguration(f"FIPS support is requested, but no_fips is set to True")
elif not self._is_fips_validated and self.dependencies["openssl"].options.no_fips:
raise ConanInvalidConfiguration(f"In order to use FIPS module from openssl/{fips_version}, it needs to be built with `no_fips` option set to False")
raise ConanInvalidConfiguration("FIPS support is requested, but no_fips is set to True")
elif not self._provides_validated_fips and self.dependencies["openssl"].options.no_fips:
raise ConanInvalidConfiguration(f"In order to use FIPS module from openssl/{self._fips_version}, it needs to be built with `no_fips` option set to False")

def build_requirements(self):
if self._settings_build.os == "Windows":
Expand Down Expand Up @@ -429,7 +432,7 @@ def _configure_args(self):
# pass no-fips to the current build if:
# - use_validated_fips is enabled and using the fips module from a different version
# - user requested no-fips
no_fips = self.options.use_validated_fips and not self._is_fips_validated or self.options.no_fips
no_fips = self.options.use_validated_fips and not self._provides_validated_fips or self.options.no_fips
args.append("no-fips" if no_fips else "enable-fips")

args.append("no-md2" if self.options.get_safe("no_md2", True) else "enable-md2")
Expand Down Expand Up @@ -589,6 +592,58 @@ def _replace_runtime_in_file(self, filename):
replace_in_file(self, filename, f"/{e} ", f"/{runtime} ", strict=False)
replace_in_file(self, filename, f"/{e}\"", f"/{runtime}\"", strict=False)

def _package_and_test_fips(self):
provdir = self._fips_provider_dir
modules_dir = os.path.join(self.package_folder, "lib", "ossl-modules")
module_filename = f"fips.{({'Macos': 'dylib', 'Windows': 'dll'}.get(str(self.settings.os), 'so'))}"
copy(self, module_filename, src=provdir, dst=modules_dir)

self.output.info("Testing FIPS module (via fipsinstall & fipsinstall -verify)")
if can_run(self):
openssl_bin = os.path.join(self.package_folder, "bin", "openssl")
fips_module = os.path.join(modules_dir, module_filename)
fips_cnf = os.path.join(self.build_folder, "fips.cnf")

# fipsinstall
fipsinstall_command = [openssl_bin, "fipsinstall", f"-module {fips_module}", f"-out {fips_cnf}"]
stderr = StringIO()
try:
self.run(" ".join(fipsinstall_command), stderr=stderr, env="conanrun")

Check failure on line 611 in recipes/openssl/3.x.x/conanfile.py

View workflow job for this annotation

GitHub Actions / Lint changed conanfile.py (v2 migration)

Unexpected keyword argument 'stderr' in method call
except ConanException as e:
stderr_text = stderr.getvalue()
raise ConanException(f"{str(e)}\n{stderr_text}") from e

stderr_text = stderr.getvalue()
self.output.info(f"{stderr_text}")
if not re.search(r"INSTALL PASSED", stderr_text):
raise ConanInvalidConfiguration(f"The FIPS Module could not be installed properly:\n{stderr_text}")

# Check version match
# Only the version of OpenSSL >= 3.1.x prints the version of the fips module at install time
# TODO: Find a better to obtain the version of the FIPS module installed
if Version(self.version) >= "3.1.0":
self.output.info("Checking FIPS version match")
fipsinstall_version_match = re.search(r"version:\s+(\S+)", stderr_text)
fipsinstall_version = fipsinstall_version_match.group(1) if fipsinstall_version_match else None

if fipsinstall_version != self._fips_version:
raise ConanInvalidConfiguration(f"The FIPS Module version installed ({fipsinstall_version}) "
f"does not match the desired version ({self._fips_version})\n{stderr_text}")

# fipsinstall -verify
verify_command = [openssl_bin, "fipsinstall", f"-module {fips_module}", f"-in {fips_cnf}", "-verify"]
stderr = StringIO()
try:
self.run(" ".join(verify_command), stderr=stderr, env="conanrun")

Check failure on line 637 in recipes/openssl/3.x.x/conanfile.py

View workflow job for this annotation

GitHub Actions / Lint changed conanfile.py (v2 migration)

Unexpected keyword argument 'stderr' in method call
except ConanException as e:
stderr_text = stderr.getvalue()
raise ConanException(f"{str(e)}\n{stderr_text}") from e

stderr_text = stderr.getvalue()
self.output.info(f"{stderr_text}")
if not re.search(r"VERIFY PASSED", stderr_text):
raise ConanInvalidConfiguration(f"The FIPS Module installation did not verify properly:\n{stderr_text}")

def package(self):
copy(self, "*LICENSE*", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
self._make_install()
Expand All @@ -605,14 +660,7 @@ def package(self):
os.unlink(os.path.join(libdir, file))

if self._is_fips_enabled:
provdir = self._fips_provider_dir
modules_dir = os.path.join(self.package_folder, "lib", "ossl-modules")
if self.settings.os == "Macos":
copy(self, "fips.dylib", src=provdir, dst=modules_dir)
elif self.settings.os == "Windows":
copy(self, "fips.dll", src=provdir, dst=modules_dir)
else:
copy(self, "fips.so", src=provdir, dst=modules_dir)
self._package_and_test_fips()

rmdir(self, os.path.join(self.package_folder, "lib", "pkgconfig"))
rmdir(self, os.path.join(self.package_folder, "lib", "cmake"))
Expand Down

0 comments on commit 3800327

Please sign in to comment.