conan-io / conan-center-index

Recipes for the ConanCenter repository
https://conan.io/center
MIT License
945 stars 1.71k forks source link

[question] How to use `openssl/3.2.1` with the FIPS module from `openssl/3.0.8` #22796

Open gegles opened 6 months ago

gegles commented 6 months ago

What is your question?

We are trying to create a FIPS compliant app. For that, we want to use the latest openssl/3.2.1 package via self.requires("openssl/3.2.1") but we also need to package the FIPS module (i.e. fips.so or fips.dll or fips.dylib) from the officially supported version, i.e. openssl/3.0.8. See the official OpenSSL doc:

Please follow the Security Policy instructions to download, build and install a validated OpenSSL FIPS provider. 
Other OpenSSL Releases MAY use the validated FIPS provider, but MUST NOT build and use their own FIPS provider. 
For example you can build OpenSSL 3.2 and use the OpenSSL 3.0.8 FIPS provider with it.

My initial solution (that sorta works) was the following:

    def build_requirements(self):
        # FIPS
        # Note: Ideally, this should be a `self.requires` in `requirements`.
        # This openssl will be built with the `build` profile when ideally it should be built with the `host` profile
        self.tool_requires(f"openssl/3.0.8")

    def requirements(self):
        self.requires("openssl/3.2.1")  # Only explicitly needed to extract the openssl executable

    def generate(self):
        # Get fips shared library
        openssl_fips = self.dependencies.build["openssl"]
        openssl_mod_dir = openssl_fips.runenv_info.vars(self)["OPENSSL_MODULES"]
        build_lib_dir = os.path.join(self.build_folder, "lib", str(self.settings.build_type), "ossl-modules")
        copy(self, "fips.*", openssl_mod_dir, build_lib_dir)
        ...

This sorta works, except for when cross-compiling (i.e. macOS Silicon -> Intel). Indeed, the fips module will be of the build arch type (armv8) instead of the host arch type (x86_64).

Ideally, it would be great if we could somehow self.requires() 2 different versions of the same package, but that might be tricky.

Otherwise, is there a way (as part of the conanfile.py and using simple methods) to merely download a separate version of a package and extract what's needed out of it?

gegles commented 6 months ago

FYI @uilianries @RubenRBS @grafikrobot @Hopobcn @Croydon @jcar87

gegles commented 5 months ago

@uilianries @RubenRBS @grafikrobot @Hopobcn @Croydon @jcar87, I know your busy, but any thoughts on this? Thx!

grafikrobot commented 5 months ago

I have no idea about openssl.

gegles commented 5 months ago

I have no idea about openssl.

I hear you, but this is actually a bit broader than openssl See:

Ideally, it would be great if we could somehow self.requires() 2 different versions of the same package, but that might be tricky.

Otherwise, is there a way (as part of the conanfile.py and using simple methods) to merely download a separate version of a package and extract what's needed out of it?

I guess my point is, there are rare cases (like this one), when it may be warranted to self.requires() 2 versions of the same package.... Is there a safe/elegant way of providing this capability?

Croydon commented 5 months ago

Your workaround with using one as a build_requirement and one as a requirement is probably the only way currently, everything else needs a change in Conan itself.

But why would you ever mix two different versions of OpenSSL in one build? If you care about FIPS then you care about certification, why would you want to use a non-certificate version at the same time in the same application?

gegles commented 5 months ago

But why would you ever mix two different versions of OpenSSL in one build? If you care about FIPS then you care about certification, why would you want to use a non-certificate version at the same time in the same application?

Thanks @Croydon! My understanding (coming from our internal security expert @kulkarniamit) is that, for the FIPS certification to be valid, one must use the exact version of the fips module (i.e. fips.dll or fips.so) and code that has been certified (i.e. v3.0.8). For the rest of the openssl libs/executables, the latest (i.e. v3.2.x) can (and probably should) be used.

Thinking about it, another approach could be to allow for some kind of recipe "alias". In other words, we could have an openssl-fips or openssl-fips-module recipe (that only includes the FIPS certified versions of openssl)...

This way, we could have the following:

def requirements(self):
        self.requires("openssl/3.2.1")
        self.requires("openssl-fips/3.0.8")

This could be an elegant way to solve this whole thing, but at the conan center level and just for that specific openssl-related situation.

Would you guys be open to that? I could work on a PR...

That alias recipe could do more or less... 1) It could literally just be pointing back to the openssl recipe, maybe with just version restrictions. Or 2) It could only bundle the fips .dll/.so/.dylib ...

LMK. Thanks!

gegles commented 5 months ago

After a quick googling, it appears that both 3.0.8 and 3.0.9 have now been FIPS validated. But this also re-emphasize the fact that all the others are not.

kulkarniamit commented 5 months ago

The FIPS Provider contains a subset of the algorithm implementations available in the default provider. The FIPS Provider only includes algorithms that have been FIPS 140-2 validated, while the default provider includes a broader set of algorithms. OpenSSL documentation recommends an approach that allows using latest release of OpenSSL 3.x with OpenSSL 3.0.8 (also 3.0.9) FIPS provider.

So, the combination of using the OpenSSL 3.0.8 FIPS Provider along with the latest OpenSSL 3.x can provide the following benefits for applications:

gegles commented 5 months ago

Ok, I've done a quick/rough PoC for a possible way to address this. Check it out and let me know. thx!

Croydon commented 5 months ago

FIPS Compliance: Using FIPS 140-2 validated cryptographic module allows applications to meet regulatory requirement

Aren't you losing this aspect in the very moment, where you are adding and using regular-non-FIPS OpenSSL components?

gegles commented 5 months ago

@Croydon, I hear what you're saying.. (and agree), but, somehow this seems to be the recommended practice by the OpenSSL devs. See here for an example (back when only 3.0.0 was FIPS and the latest non-FIPS was 3.0.8).

@kulkarniamit may be able to provide better pointers from the docs...

gegles commented 5 months ago

@Croydon, see my minor tweak/improvement here to the openssl recipe. With this, I am able to have a bare minimum recipes/openssl-fips folder that simply points back to the recipes/openssl With this config.yaml:

versions:
  # Note: Only ever add the versions of OpenSSL that have been FIPS certified
  3.0.9:
    folder: "../openssl/3.x.x"
  3.0.8:
    folder: "../openssl/3.x.x"

This allows me to run the following command:

conan create -b missing --version 3.0.9 --name openssl-fips ../openssl/3.x.x

It all works, but with a key issue... for this to work I to do two things:

Obviously, some package name customization mechanism would need to be provided by the conan center... i.e. it could either pick up the name from the folder name (when not specified in the recipe) or from the config.yaml maybe?

Overall, I believe this whole mechanism would be very low effort, have no redundancy in the recipe definition (there would still only be one openssl recipe) and it does beautifully address exactly this "unusual" requirement from the OpenSSL package...

Any thoughts?

kulkarniamit commented 5 months ago

FIPS Compliance: Using FIPS 140-2 validated cryptographic module allows applications to meet regulatory requirement

Aren't you losing this aspect in the very moment, where you are adding and using regular-non-FIPS OpenSSL components?

FIPS provider in OpenSSL 3 is a dynamically loadable provider. Applications can offer a switch/option to enable/disable FIPS provider offering a choice between FIPS approved implementations and regular non-fips components. This would allow users to switch between fips and default provider (non-fips) implementations based on their regulatory compliance requirements. Adding a regular non-fips openssl provider does not make the application non-compliant. Using a non-fips implementation makes it non-compliant. So, the application must have strict controls to ensure FIPS approved implementations are used when FIPS switch is turned on. Shipping a default provider (3.x) with a FIPS provider (3.0.8/3.0.9) allows this flexibility for applications to switch based on user requirement.

jcar87 commented 4 months ago

It may be possible for the openssl recipe itself to have logic like this - and more closely follow the instructions:

https://github.com/openssl/openssl/blob/master/README-FIPS.md#installing-the-fips-provider-and-using-it-with-the-latest-release

The below snipped does address the issue that you mentioned about the host context.

class OpenSSLConan(ConanFile):
    name = "openssl"
    version = "3.2.1"

    def requirements(self):
          if self.options.use_validated_fips:
               self.requires("openssl/3.0.8", visible=False, libs=False, headers=False, run=False)

    def package(self):
         self.run("make install:)

         # copy fips module from the openssl/3.0.8 dependency into the package foler

I believe the FIPS certification involves some sort of testing to validate it's working properly, this would have to be done both at package time (with wrap.pl and make test as advised in the OpenSSL documentation, as well as the test_package. Ensuring that this process passes would be the responsibility of the users.

This involves the openssl recipe depending on an earlier version of itself, in a way that is visible only to itself and does not propagate nor pollute the build process. If this doesn't currently work we are exploring the possibility of addressing this in the next Conan 2 release. There's some questions around option propagation that we need to consider, which also reinforces the notion of running the tests at package time and the consumer test in test_package.

This would avoid having to make further changes to the recipe/cmake, in particular the complexity surrounding having two references from the same recipe file. If this is something that can be fixed in the recipe with Conan features, let's do it :D

gegles commented 4 months ago

Yes, I like this a lot and I do think it would be the most elegant, logical and easiest way to provide the openssl package with the proper FIPS module... Thx!

jcar87 commented 4 months ago

Thanks @gegles - thank you for providing details and for experimenting with this, it's been really helpful.

jcar87 commented 4 months ago

@gegles -

We are experimenting with the feature on this branch: https://github.com/conan-io/conan/pull/16132/files memsharded:feature/require_bootstrap

If you are willing to try it out, you can probably install it in a python virtual environment with

pip install git+https://github.com/memsharded/conan.git@feature/require_bootstrap

Key points:

Nekto89 commented 1 month ago

Does anyone have some working prototype? I wonder how override/force will work in case of several versions.

gegles commented 1 month ago

Does anyone have some working prototype? I wonder how override/force will work in case of several versions.

@Nekto89, I haven't gotten around to write that updated recipe with the use_validated_fips option. Feel free to implement it as per @jcar87's suggestion ;-)

You bring up a good point... I guess override/force would change the main version of the package, but the version of the validated fips used would remain the same "hardcoded" 3.0.8 ...

gegles commented 1 month ago

@Nekto89 see here for my first attempt to solve this.

Nekto89 commented 1 month ago

@Nekto89 see here for my first attempt to solve this.

Just for history: I've tried changes in PR on MSVC. For version override I had to add [replace_requires] to host profile because it's not clear how to do that through conanfile.py which already has "main openssl version". Zlib requirements of 3.0.9 can't be overriden without replace_requires too. I have slightly customized recipe that allows me to replace zlib with zlib-ng.

def requirements(self):
    self.requires("openssl/3.0.14@user/channel#4faa23d318801e3887bc428ace1f8e4d", force=True)
    self.requires("zlib-ng/2.1.6@user/channel#fac0999d78a3e7ab7c4cc96035a28931", force=True)
[options]
openssl/3.0.14*:no_fips=True
openssl/*:no_legacy=True
openssl/3.0.14*:use_validated_fips=True
openssl/*:with_zlibng=True

[replace_requires]
openssl/3.0.9 : openssl/3.0.9@user/channel#4faa23d318801e3887bc428ace1f8e4d
zlib-ng/* : zlib-ng/2.1.6@user/channel#fac0999d78a3e7ab7c4cc96035a28931