Skip to content

Commit

Permalink
openssl: add use_validated_fips option
Browse files Browse the repository at this point in the history
  • Loading branch information
gegles committed Jan 10, 2025
1 parent 5df245d commit 4bcd024
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 13 deletions.
8 changes: 8 additions & 0 deletions recipes/openssl/3.x.x/conandata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ sources:
3.0.15: # LTS: keep the most recent 3.0.x until a new LTS is designated
url: "https://github.com/openssl/openssl/releases/download/openssl-3.0.15/openssl-3.0.15.tar.gz"
sha256: 23c666d0edf20f14249b3d8f0368acaee9ab585b09e1de82107c66e1f3ec9533
# Validated FIPS versions
3.0.9:
url: "https://github.com/openssl/openssl/releases/download/openssl-3.0.9/openssl-3.0.9.tar.gz"
sha256: eb1ab04781474360f77c318ab89d8c5a03abc38e63d65a603cabbf1b00a1dc90
3.0.8:
url: "https://github.com/openssl/openssl/releases/download/openssl-3.0.8/openssl-3.0.8.tar.gz"
sha256: 6c13d2bf38fdf31eac3ce2a347073673f5d63263398f1f69d0df4a41253e4b3e
#
126 changes: 113 additions & 13 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 @@ -89,6 +92,7 @@ class OpenSSLConan(ConanFile):
"no_zlib": [True, False],
"openssldir": [None, "ANY"],
"tls_security_level": [None, 0, 1, 2, 3, 4, 5],
"use_validated_fips": [True, False],
}
default_options = {key: False for key in options.keys()}
default_options["fPIC"] = True
Expand All @@ -109,6 +113,37 @@ def _is_mingw(self):
def _use_nmake(self):
return self._is_clang_cl or is_msvc(self)

@property
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 _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._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")
else:
return None

def config_options(self):
if self.settings.os != "Windows":
self.options.rm_safe("capieng_dialog")
Expand All @@ -134,6 +169,13 @@ def requirements(self):
if not self.options.no_zlib:
self.requires("zlib/[>=1.2.11 <2]")

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. {self._fips_version})")

def validate(self):
if self.settings.os == "Emscripten":
if not all((self.options.no_asm, self.options.no_threads, self.options.no_stdio)):
Expand All @@ -142,6 +184,14 @@ def validate(self):
if self.settings.os == "iOS" and self.options.shared:
raise ConanInvalidConfiguration("OpenSSL 3 does not support building shared libraries for iOS")

if self.options.use_validated_fips:
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("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":
if self.conf.get("user.openssl:windows_use_jom", False):
Expand Down Expand Up @@ -377,7 +427,12 @@ def _configure_args(self):
else:
args.append("-fPIC" if self.options.get_safe("fPIC", True) else "no-pic")

args.append("no-fips" if self.options.get_safe("no_fips", True) else "enable-fips")
# 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._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")
if str(self.options.tls_security_level) != "None":
args.append(f"-DOPENSSL_TLS_SECURITY_LEVEL={self.options.tls_security_level}")
Expand Down Expand Up @@ -408,7 +463,7 @@ def _configure_args(self):
])

for option_name in self.default_options.keys():
if self.options.get_safe(option_name, False) and option_name not in ("shared", "fPIC", "openssldir", "tls_security_level", "capieng_dialog", "enable_capieng", "zlib", "no_fips", "no_md2"):
if self.options.get_safe(option_name, False) and option_name not in ("shared", "fPIC", "openssldir", "tls_security_level", "capieng_dialog", "enable_capieng", "zlib", "no_fips", "no_md2", "use_validated_fips"):
self.output.info(f"Activated option: {option_name}")
args.append(option_name.replace("_", "-"))
return args
Expand Down Expand Up @@ -547,6 +602,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")
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")
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 @@ -562,15 +669,8 @@ def package(self):
if file.endswith(".a"):
os.unlink(os.path.join(libdir, file))

if not self.options.no_fips:
provdir = os.path.join(self.source_folder, "providers")
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)
if self._is_fips_enabled:
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
6 changes: 6 additions & 0 deletions recipes/openssl/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@ versions:
folder: "3.x.x"
"3.0.15":
folder: "3.x.x"
# Validated FIPS versions
"3.0.9":
folder: "3.x.x"
"3.0.8":
folder: "3.x.x"
#
"1.1.1w":
folder: "1.x.x"

0 comments on commit 4bcd024

Please sign in to comment.