Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openssl: add use_validated_fips option #24709

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -32,3 +32,11 @@ sources:
3.0.13:
url: "https://github.com/openssl/openssl/releases/download/openssl-3.0.13/openssl-3.0.13.tar.gz"
sha256: 88525753f79d3bec27d2fa7c66aa0b92b3aa9498dafd93d7cfa4b3780cdae313
# 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 @@ -113,6 +117,37 @@ def _use_nmake(self):
def _settings_build(self):
return getattr(self, "settings_build", self.settings)

@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 @@ -138,6 +173,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 @@ -146,6 +188,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 not self.options.no_asm:
Expand Down Expand Up @@ -379,7 +429,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 @@ -410,7 +465,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 @@ -545,6 +600,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 @@ -560,15 +667,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 @@ -21,5 +21,11 @@ versions:
folder: "3.x.x"
"3.0.13":
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"
Loading