[libcamera-devel] libcamera: ipa: allow trusting modules by checksum
diff mbox series

Message ID 20240120143742.1302914-1-arnout@bzzt.net
State New
Headers show
Series
  • [libcamera-devel] libcamera: ipa: allow trusting modules by checksum
Related show

Commit Message

Arnout Engelen Jan. 20, 2024, 2:37 p.m. UTC
Currently, libcamera signs the in-tree IPA modules during the build, and embeds
the public key so that only trusted IPA modules will be run in-process.
Out-of-tree modules will be run using runtime isolation.

This commit adds a second mechanism to achieve the same: during packaging the
checksums of the in-tree IPA modules are recorded, and at run time the modules
are considered trusted when they match the recorded trusted checksums.

The motivation behind adding this mechanism is that this allows rebuilding the
library and getting a bit-by-bit identical result, without having to share the
keys with which to sign the trusted modules. This is known as 'Reproducible
Builds', and you can read more about its advantages on
https://reproducible-builds.org/. With this feature, packagers that care about
reproducible builds can disable the module signing, and enjoy equivalent
security and performance while also allowing independent rebuilds.

Signed-off-by: Arnout Engelen <arnout@bzzt.net>
---
 Documentation/environment_variables.rst  |  3 +
 include/libcamera/internal/ipa_manager.h | 10 ++-
 include/libcamera/internal/ipa_module.h  |  2 +
 meson_options.txt                        |  4 +
 src/apps/ipa-verify/main.cpp             | 45 ++++++++++-
 src/apps/ipa-verify/meson.build          |  2 +-
 src/ipa/ipa-checksum-install.sh          | 22 ++++++
 src/ipa/meson.build                      | 11 +++
 src/libcamera/ipa_manager.cpp            | 99 ++++++++++++++++++++++--
 src/libcamera/ipa_module.cpp             | 30 +++++++
 src/meson.build                          | 17 +++-
 11 files changed, 229 insertions(+), 16 deletions(-)
 create mode 100644 src/ipa/ipa-checksum-install.sh

Comments

Elias Naur Jan. 21, 2024, 2:09 p.m. UTC | #1
Hi Arnout,

> The motivation behind adding this mechanism is that this allows rebuilding the
> library and getting a bit-by-bit identical result, without having to share the
> keys with which to sign the trusted modules. This is known as 'Reproducible
> Builds', and you can read more about its advantages on
> https://reproducible-builds.org/. With this feature, packagers that care about
> reproducible builds can disable the module signing, and enjoy equivalent
> security and performance while also allowing independent rebuilds.

Thanks for working on reproducible builds. I locally hack libcamera
to achieve bit-for-bit reproducible builds, and look forward to no longer needing
that hack.

I think the feature would even more useful if it were always enabled. In particular,
I propose to:

- Always enable checksums.
- Embed the known checksums into the binary, not in a separate configuration file.
- Don't sign IPAs that have known checksums (thus achieving bit-for-bit reproducibility).
  - In your patch, I believe this is equivalent to "ipa_sign_modules" always being false.

Optionally,

- Avoid duplicating the SHA256 digest by re-using the digest done in libcamera/pub_key.cpp.

Thanks,
Elias
Kieran Bingham Jan. 21, 2024, 4:32 p.m. UTC | #2
Quoting Elias Naur via libcamera-devel (2024-01-21 14:09:52)
> Hi Arnout,
> 
> > The motivation behind adding this mechanism is that this allows rebuilding the
> > library and getting a bit-by-bit identical result, without having to share the
> > keys with which to sign the trusted modules. This is known as 'Reproducible
> > Builds', and you can read more about its advantages on
> > https://reproducible-builds.org/. With this feature, packagers that care about
> > reproducible builds can disable the module signing, and enjoy equivalent
> > security and performance while also allowing independent rebuilds.
> 
> Thanks for working on reproducible builds. I locally hack libcamera
> to achieve bit-for-bit reproducible builds, and look forward to no longer needing
> that hack.

I agree, finding a solution to handle reproducible builds is a good
goal.


> I think the feature would even more useful if it were always enabled. In particular,
> I propose to:
> 
> - Always enable checksums.
> - Embed the known checksums into the binary, not in a separate configuration file.

I think this is a fairly important requirement to be able to upstream a
reproducilble builds solution. The checksums should be stored within the
libcamera binary so the configuration file can not be amended after the
fact, which would otherwise defeat the purpose. 

But this makes things much more difficult I believe...

The tricky parts here will be handling how to verify the checksum of the
modules while distributions do actions such as stripping symbols. There
are legitimate modifications that can be made to the module as part of
the installation process which would then break the checksum
verifications.



> - Don't sign IPAs that have known checksums (thus achieving bit-for-bit reproducibility).
>   - In your patch, I believe this is equivalent to "ipa_sign_modules" always being false.

Would there be a mix of signed+checksummed modules in a given
distribution?

> 
> Optionally,
> 
> - Avoid duplicating the SHA256 digest by re-using the digest done in libcamera/pub_key.cpp.
> 
> Thanks,
> Elias
Elias Naur Jan. 21, 2024, 5:33 p.m. UTC | #3
On Sun, 21 Jan 2024 at 11:32, Kieran Bingham
<kieran.bingham@ideasonboard.com> wrote:
>
> Quoting Elias Naur via libcamera-devel (2024-01-21 14:09:52)
> > Hi Arnout,
> >
>
> > I think the feature would even more useful if it were always enabled. In particular,
> > I propose to:
> >
> > - Always enable checksums.
> > - Embed the known checksums into the binary, not in a separate configuration file.
>
> I think this is a fairly important requirement to be able to upstream a
> reproducilble builds solution. The checksums should be stored within the
> libcamera binary so the configuration file can not be amended after the
> fact, which would otherwise defeat the purpose.
>
> But this makes things much more difficult I believe...
>
> The tricky parts here will be handling how to verify the checksum of the
> modules while distributions do actions such as stripping symbols. There
> are legitimate modifications that can be made to the module as part of
> the installation process which would then break the checksum
> verifications.
>
>

I don't know the relative difficulty level, but I just want to throw
out another possibility
that completely sidesteps verification: linking the modules directly
into libcamera. That would
also move libcamera closer to being statically linkable into
executables. FWIW, libcamera is
the only blocker to a completely static build of my project.

>
> > - Don't sign IPAs that have known checksums (thus achieving bit-for-bit reproducibility).
> >   - In your patch, I believe this is equivalent to "ipa_sign_modules" always being false.
>
> Would there be a mix of signed+checksummed modules in a given
> distribution?
>

As far as I understand, the signing feature is only for out-of-tree
(closed source?) modules.
Assuming a Linux distribution without such modules, I see no use for signing.

Elias
Laurent Pinchart Jan. 21, 2024, 6:54 p.m. UTC | #4
On Sun, Jan 21, 2024 at 04:32:06PM +0000, Kieran Bingham via libcamera-devel wrote:
> Quoting Elias Naur via libcamera-devel (2024-01-21 14:09:52)
> > Hi Arnout,
> > 
> > > The motivation behind adding this mechanism is that this allows rebuilding the
> > > library and getting a bit-by-bit identical result, without having to share the
> > > keys with which to sign the trusted modules. This is known as 'Reproducible
> > > Builds', and you can read more about its advantages on
> > > https://reproducible-builds.org/. With this feature, packagers that care about
> > > reproducible builds can disable the module signing, and enjoy equivalent
> > > security and performance while also allowing independent rebuilds.
> > 
> > Thanks for working on reproducible builds. I locally hack libcamera
> > to achieve bit-for-bit reproducible builds, and look forward to no longer needing
> > that hack.
> 
> I agree, finding a solution to handle reproducible builds is a good
> goal.
> 
> > I think the feature would even more useful if it were always enabled. In particular,
> > I propose to:
> > 
> > - Always enable checksums.
> > - Embed the known checksums into the binary, not in a separate configuration file.
> 
> I think this is a fairly important requirement to be able to upstream a
> reproducilble builds solution. The checksums should be stored within the
> libcamera binary so the configuration file can not be amended after the
> fact, which would otherwise defeat the purpose. 
> 
> But this makes things much more difficult I believe...
> 
> The tricky parts here will be handling how to verify the checksum of the
> modules while distributions do actions such as stripping symbols. There
> are legitimate modifications that can be made to the module as part of
> the installation process which would then break the checksum
> verifications.

Checksums in a configuration file is a no-go I'm afraid, as it means
anyone could ship a closed-source IPA module and instruct users to add
an entry to the configuration file, circumventing IPA module isolation.

> > - Don't sign IPAs that have known checksums (thus achieving bit-for-bit reproducibility).
> >   - In your patch, I believe this is equivalent to "ipa_sign_modules" always being false.
> 
> Would there be a mix of signed+checksummed modules in a given
> distribution?
> 
> > Optionally,
> > 
> > - Avoid duplicating the SHA256 digest by re-using the digest done in libcamera/pub_key.cpp.
Arnout Engelen Jan. 22, 2024, 8:50 a.m. UTC | #5
Hi all,

On Sun, Jan 21, 2024, at 19:54, Laurent Pinchart wrote:
> On Sun, Jan 21, 2024 at 04:32:06PM +0000, Kieran Bingham via libcamera-devel wrote:
> > Quoting Elias Naur via libcamera-devel (2024-01-21 14:09:52)
> > > Hi Arnout,
> > > 
> > > > The motivation behind adding this mechanism is that this allows rebuilding the
> > > > library and getting a bit-by-bit identical result, without having to share the
> > > > keys with which to sign the trusted modules. This is known as 'Reproducible
> > > > Builds', and you can read more about its advantages on
> > > > https://reproducible-builds.org/. With this feature, packagers that care about
> > > > reproducible builds can disable the module signing, and enjoy equivalent
> > > > security and performance while also allowing independent rebuilds.
> > > 
> > > Thanks for working on reproducible builds. I locally hack libcamera
> > > to achieve bit-for-bit reproducible builds, and look forward to no longer needing
> > > that hack.
> > 
> > I agree, finding a solution to handle reproducible builds is a good
> > goal.

Thanks for the encouragement!

> > > I think the feature would even more useful if it were always enabled. In particular,
> > > I propose to:
> > > 
> > > - Always enable checksums.
> > > - Embed the known checksums into the binary, not in a separate configuration file.
> > 
> > I think this is a fairly important requirement to be able to upstream a
> > reproducilble builds solution. The checksums should be stored within the
> > libcamera binary so the configuration file can not be amended after the
> > fact, which would otherwise defeat the purpose. 
> > 
> > But this makes things much more difficult I believe...
> > 
> > The tricky parts here will be handling how to verify the checksum of the
> > modules while distributions do actions such as stripping symbols. There
> > are legitimate modifications that can be made to the module as part of
> > the installation process which would then break the checksum
> > verifications.

Indeed: embedding the checksums in the binary was my initial approach, but it's tricky given the legitimate use cases for modifying the objects in the install phase.

> Checksums in a configuration file is a no-go I'm afraid, as it means
> anyone could ship a closed-source IPA module and instruct users to add
> an entry to the configuration file, circumventing IPA module isolation.

It depends on your threat model / risk appetite, of course - it's hard to protect against users that are happy to explicitly trust such a third-party module. Going to extremes, someone could even try to convince such users to load a version of libcamera that trusts their module, or other terrible hacks. I guess we don't want to make it too easy, though.

I'd be happy to provide a version of this patch with the 'LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE' environment variable support removed, and a meson option to enable/disable trusting checksums - default value up to you. That may increase the barrier and give distributions a chance to make their own trade-off?

(I also like Elias' idea of statically linking the in-tree modules, but I don't think I'm comfortable enough with the codebase to take that on)

> > > - Don't sign IPAs that have known checksums (thus achieving bit-for-bit reproducibility).
> > >   - In your patch, I believe this is equivalent to "ipa_sign_modules" always being false.
> > 
> > Would there be a mix of signed+checksummed modules in a given
> > distribution?

I don't see an obvious use case for having both, but I don't see a reason to restrict it either.


Kind regards,

Arnout
Laurent Pinchart Jan. 22, 2024, 9:27 a.m. UTC | #6
On Mon, Jan 22, 2024 at 09:50:02AM +0100, Arnout Engelen wrote:
> On Sun, Jan 21, 2024, at 19:54, Laurent Pinchart wrote:
> > On Sun, Jan 21, 2024 at 04:32:06PM +0000, Kieran Bingham via libcamera-devel wrote:
> > > Quoting Elias Naur via libcamera-devel (2024-01-21 14:09:52)
> > > > Hi Arnout,
> > > > 
> > > > > The motivation behind adding this mechanism is that this allows rebuilding the
> > > > > library and getting a bit-by-bit identical result, without having to share the
> > > > > keys with which to sign the trusted modules. This is known as 'Reproducible
> > > > > Builds', and you can read more about its advantages on
> > > > > https://reproducible-builds.org/. With this feature, packagers that care about
> > > > > reproducible builds can disable the module signing, and enjoy equivalent
> > > > > security and performance while also allowing independent rebuilds.
> > > > 
> > > > Thanks for working on reproducible builds. I locally hack libcamera
> > > > to achieve bit-for-bit reproducible builds, and look forward to no longer needing
> > > > that hack.
> > > 
> > > I agree, finding a solution to handle reproducible builds is a good
> > > goal.
> 
> Thanks for the encouragement!
> 
> > > > I think the feature would even more useful if it were always enabled. In particular,
> > > > I propose to:
> > > > 
> > > > - Always enable checksums.
> > > > - Embed the known checksums into the binary, not in a separate configuration file.
> > > 
> > > I think this is a fairly important requirement to be able to upstream a
> > > reproducilble builds solution. The checksums should be stored within the
> > > libcamera binary so the configuration file can not be amended after the
> > > fact, which would otherwise defeat the purpose. 
> > > 
> > > But this makes things much more difficult I believe...
> > > 
> > > The tricky parts here will be handling how to verify the checksum of the
> > > modules while distributions do actions such as stripping symbols. There
> > > are legitimate modifications that can be made to the module as part of
> > > the installation process which would then break the checksum
> > > verifications.
> 
> Indeed: embedding the checksums in the binary was my initial approach,
> but it's tricky given the legitimate use cases for modifying the
> objects in the install phase.

There's also a chicken-and-egg issue I believe, as the in-tree IPA
modules have to link against the libcamera binary, and checksums can
only be computed after the IPA modules have been linked.

> > Checksums in a configuration file is a no-go I'm afraid, as it means
> > anyone could ship a closed-source IPA module and instruct users to add
> > an entry to the configuration file, circumventing IPA module isolation.
> 
> It depends on your threat model / risk appetite, of course - it's hard
> to protect against users that are happy to explicitly trust such a
> third-party module. Going to extremes, someone could even try to
> convince such users to load a version of libcamera that trusts their
> module, or other terrible hacks. I guess we don't want to make it too
> easy, though.

That's the idea, yes. libcamera being open-source, users can ultimately
disable any protection method we put in place. We don't want to make it
easy to circumvent this, not so much towards the users, but towards the
closed-source IPA module providers.

> I'd be happy to provide a version of this patch with the
> 'LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE' environment variable
> support removed, and a meson option to enable/disable trusting
> checksums - default value up to you. That may increase the barrier and
> give distributions a chance to make their own trade-off?

I'm afraid I'm still not comfortable with that. If we want to use
checksums, I think we need to embed them in the libcamera binary.

> (I also like Elias' idea of statically linking the in-tree modules,
> but I don't think I'm comfortable enough with the codebase to take
> that on)

I've been sleeping over this, and it's an interesting idea to explore I
think. There will be technical issues to solve though, as we want to
make it possible for users to select between different IPA modules for
the same platform.

> > > > - Don't sign IPAs that have known checksums (thus achieving bit-for-bit reproducibility).
> > > >   - In your patch, I believe this is equivalent to "ipa_sign_modules" always being false.
> > > 
> > > Would there be a mix of signed+checksummed modules in a given
> > > distribution?
> 
> I don't see an obvious use case for having both, but I don't see a
> reason to restrict it either.
Arnout Engelen Jan. 29, 2024, 4:15 p.m. UTC | #7
On Mon, Jan 22, 2024, at 10:27, Laurent Pinchart wrote:
> > > Checksums in a configuration file is a no-go I'm afraid, as it means
> > > anyone could ship a closed-source IPA module and instruct users to add
> > > an entry to the configuration file, circumventing IPA module isolation.
> >
> > I'd be happy to provide a version of this patch with the
> > 'LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE' environment variable
> > support removed, and a meson option to enable/disable trusting
> > checksums - default value up to you. That may increase the barrier and
> > give distributions a chance to make their own trade-off?
> 
> I'm afraid I'm still not comfortable with that. If we want to use
> checksums, I think we need to embed them in the libcamera binary.

I created a variation on the patch that does this at https://lists.libcamera.org/pipermail/libcamera-devel/2024-January/040244.html. Happy to finish it if the general approach looks acceptable.

> > (I also like Elias' idea of statically linking the in-tree modules,
> > but I don't think I'm comfortable enough with the codebase to take
> > that on)
> 
> I've been sleeping over this, and it's an interesting idea to explore I
> think. There will be technical issues to solve though, as we want to
> make it possible for users to select between different IPA modules for
> the same platform.

(this might still be interesting, but if it doesn't materialize let's not stall the other solution for it :) )


Kind regards,

Arnout

Patch
diff mbox series

diff --git a/Documentation/environment_variables.rst b/Documentation/environment_variables.rst
index a9b230bc..baae225c 100644
--- a/Documentation/environment_variables.rst
+++ b/Documentation/environment_variables.rst
@@ -32,6 +32,9 @@  LIBCAMERA_IPA_FORCE_ISOLATION
 
    Example value: ``1``
 
+LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE
+   Define custom location of the trusted IPA module checksums file.
+
 LIBCAMERA_IPA_MODULE_PATH
    Define custom search locations for IPA modules (`more <IPA module_>`__).
 
diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h
index bf823563..c05583e8 100644
--- a/include/libcamera/internal/ipa_manager.h
+++ b/include/libcamera/internal/ipa_manager.h
@@ -38,7 +38,7 @@  public:
 		if (!m)
 			return nullptr;
 
-		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self_->isSignatureValid(m));
+		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self_->isTrusted(m));
 		if (!proxy->isValid()) {
 			LOG(IPAManager, Error) << "Failed to load proxy";
 			return nullptr;
@@ -53,6 +53,8 @@  public:
 		return pubKey_;
 	}
 #endif
+	static int loadTrustedChecksums(const char *path,
+					std::vector<std::vector<uint8_t>> &checksums);
 
 private:
 	static IPAManager *self_;
@@ -65,6 +67,8 @@  private:
 			  uint32_t maxVersion);
 
 	bool isSignatureValid(IPAModule *ipa) const;
+	bool isTrustedChecksum(IPAModule *ipa) const;
+	bool isTrusted(IPAModule *ipa) const;
 
 	std::vector<IPAModule *> modules_;
 
@@ -72,6 +76,10 @@  private:
 	static const uint8_t publicKeyData_[];
 	static const PubKey pubKey_;
 #endif
+
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+	std::vector<std::vector<uint8_t>> trusted_checksums_;
+#endif
 };
 
 } /* namespace libcamera */
diff --git a/include/libcamera/internal/ipa_module.h b/include/libcamera/internal/ipa_module.h
index 8038bdee..f4c0ec5c 100644
--- a/include/libcamera/internal/ipa_module.h
+++ b/include/libcamera/internal/ipa_module.h
@@ -30,6 +30,7 @@  public:
 
 	const struct IPAModuleInfo &info() const;
 	const std::vector<uint8_t> signature() const;
+	const std::vector<uint8_t> checksum() const;
 	const std::string &path() const;
 
 	bool load();
@@ -47,6 +48,7 @@  private:
 
 	struct IPAModuleInfo info_;
 	std::vector<uint8_t> signature_;
+	std::vector<uint8_t> checksum_;
 
 	std::string libPath_;
 	bool valid_;
diff --git a/meson_options.txt b/meson_options.txt
index 5fdc7be8..a970ac30 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -30,6 +30,10 @@  option('ipas',
         choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'vimc'],
         description : 'Select which IPA modules to build')
 
+option('ipa_sign_modules',
+        type : 'feature',
+        description : 'enable IPA trusted module signing')
+
 option('lc-compliance',
         type : 'feature',
         value : 'auto',
diff --git a/src/apps/ipa-verify/main.cpp b/src/apps/ipa-verify/main.cpp
index 76ba5073..368de4a0 100644
--- a/src/apps/ipa-verify/main.cpp
+++ b/src/apps/ipa-verify/main.cpp
@@ -18,8 +18,9 @@  using namespace libcamera;
 
 namespace {
 
-bool isSignatureValid(IPAModule *ipa)
+bool isSignatureValid([[maybe_unused]] IPAModule *ipa)
 {
+#if HAVE_IPA_PUBKEY
 	File file{ ipa->path() };
 	if (!file.open(File::OpenModeFlag::ReadOnly))
 		return false;
@@ -29,6 +30,32 @@  bool isSignatureValid(IPAModule *ipa)
 		return false;
 
 	return IPAManager::pubKey().verify(data, ipa->signature());
+#else
+	return false;
+#endif
+}
+
+bool isChecksumTrusted([[maybe_unused]] IPAModule *ipa)
+{
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+	std::vector<std::vector<uint8_t>> trusted;
+	const char *checksumsFile =
+		utils::secure_getenv("LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE");
+	if (checksumsFile) {
+		IPAManager::loadTrustedChecksums(checksumsFile, trusted);
+	} else {
+		IPAManager::loadTrustedChecksums(IPA_TRUSTED_MODULE_CHECKSUMS_FILE, trusted);
+	}
+	for (std::vector<uint8_t> t : trusted) {
+		if (std::equal(t.begin(), t.begin() + 32,
+			       ipa->checksum().begin())) {
+			return true;
+		}
+	}
+	return false;
+#else
+	return false;
+#endif
 }
 
 void usage(char *argv0)
@@ -54,11 +81,23 @@  int main(int argc, char **argv)
 		return EXIT_FAILURE;
 	}
 
-	if (!isSignatureValid(&module)) {
+	bool ok = false;
+	if (isSignatureValid(&module)) {
+		std::cout << "IPA module signature is valid" << std::endl;
+		ok = true;
+	} else {
 		std::cout << "IPA module signature is invalid" << std::endl;
+	}
+	if (isChecksumTrusted(&module)) {
+		std::cout << "IPA module checksum is trusted" << std::endl;
+		ok = true;
+	} else {
+		std::cout << "IPA module checksum is not trusted" << std::endl;
+	}
+
+	if (!ok) {
 		return EXIT_FAILURE;
 	}
 
-	std::cout << "IPA module signature is valid" << std::endl;
 	return 0;
 }
diff --git a/src/apps/ipa-verify/meson.build b/src/apps/ipa-verify/meson.build
index 7fdda3b9..feffb538 100644
--- a/src/apps/ipa-verify/meson.build
+++ b/src/apps/ipa-verify/meson.build
@@ -1,6 +1,6 @@ 
 # SPDX-License-Identifier: CC0-1.0
 
-if not ipa_sign_module
+if not ipa_sign_module and not ipa_checksum_trusted_modules
     subdir_done()
 endif
 
diff --git a/src/ipa/ipa-checksum-install.sh b/src/ipa/ipa-checksum-install.sh
new file mode 100644
index 00000000..d8341c0c
--- /dev/null
+++ b/src/ipa/ipa-checksum-install.sh
@@ -0,0 +1,22 @@ 
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2020, Google Inc.
+#
+# Author: Arnout Engelen <arnout@bzzt.net>
+#
+# ipa-checksum-install.sh - Generate IPA module checksums when installing
+
+modules=$*
+
+echo "Generating trusted IPA module checksums"
+
+checksum_file=${MESON_INSTALL_DESTDIR_PREFIX}/share/libcamera/ipa/trusted_module_checksums.txt
+
+rm ${checksum_file} || true
+
+for module in ${modules} ; do
+	module="${MESON_INSTALL_DESTDIR_PREFIX}/${module}"
+	if [ -f "${module}" ] ; then
+        sha256sum -b "${module}" | sed -e "s| .*/| |" >> ${checksum_file}
+	fi
+done
diff --git a/src/ipa/meson.build b/src/ipa/meson.build
index 48793e07..c3ae24ba 100644
--- a/src/ipa/meson.build
+++ b/src/ipa/meson.build
@@ -8,6 +8,10 @@  ipa_install_dir = libcamera_libdir
 ipa_data_dir = libcamera_datadir / 'ipa'
 ipa_sysconf_dir = libcamera_sysconfdir / 'ipa'
 
+config_h.set('IPA_TRUSTED_MODULE_CHECKSUMS_FILE',
+             '"' + get_option('prefix') / ipa_data_dir
+                 + '/trusted_module_checksums.txt"')
+
 config_h.set('IPA_CONFIG_DIR',
              '"' + get_option('prefix') / ipa_sysconf_dir +
              ':' + get_option('prefix') / ipa_data_dir + '"')
@@ -75,3 +79,10 @@  if ipa_sign_module
                              enabled_ipa_modules,
                              install_tag : 'runtime')
 endif
+
+if ipa_checksum_trusted_modules
+    # Similarly, calculate checksums for the installed artifacts
+    meson.add_install_script('ipa-checksum-install.sh',
+                             enabled_ipa_modules,
+                             install_tag : 'runtime')
+endif
diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index 7a4515d9..beb93de6 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -114,6 +114,18 @@  IPAManager::IPAManager()
 		LOG(IPAManager, Warning) << "Public key not valid";
 #endif
 
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+	const char *checksumsFile =
+		utils::secure_getenv("LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE");
+	if (checksumsFile) {
+		IPAManager::loadTrustedChecksums(checksumsFile,
+						 trusted_checksums_);
+	} else {
+		IPAManager::loadTrustedChecksums(IPA_TRUSTED_MODULE_CHECKSUMS_FILE,
+						 trusted_checksums_);
+	}
+#endif
+
 	unsigned int ipaCount = 0;
 
 	/* User-specified paths take precedence. */
@@ -165,6 +177,41 @@  IPAManager::~IPAManager()
 	self_ = nullptr;
 }
 
+/**
+ * \brief Load trusted checksums
+ * \param[in] path The path to the file containing the trusted checksums
+ * \param[out] checksums A vector of checksums as 32-element byte vectors
+ *
+ * \return Zero on success, an error code on failure.
+ */
+int IPAManager::loadTrustedChecksums([[maybe_unused]] const char *path, [[maybe_unused]] std::vector<std::vector<uint8_t>> &checksums)
+{
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+	File file{ path };
+	if (!file.open(File::OpenModeFlag::ReadOnly)) {
+		LOG(IPAManager, Warning) << "Failed to open trusted IPA checksums: "
+					 << strerror(-file.error());
+		return file.error();
+	}
+	Span<const uint8_t> span = file.map();
+	const uint8_t* data = span.data();
+	const int size = span.size();
+	for (int i = 0; i < size - 64;) {
+		std::vector<uint8_t> *checksum = new std::vector<uint8_t>();
+		for (int c = 0; c < 64; c += 2) {
+			char chr[3] = { static_cast<char>(data[i+c]), static_cast<char>(data[i+c+1]), '\0' };
+			checksum->push_back(strtol(chr, nullptr, 16));
+		}
+		checksums.push_back(*checksum);
+		while (data[i] != '\n' && i < size) {
+			i++;
+		};
+		i++;
+	}
+#endif
+	return 0;
+}
+
 /**
  * \brief Identify shared library objects within a directory
  * \param[in] libDir The directory to search for shared objects
@@ -295,14 +342,6 @@  IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
 bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
 {
 #if HAVE_IPA_PUBKEY
-	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
-	if (force && force[0] != '\0') {
-		LOG(IPAManager, Debug)
-			<< "Isolation of IPA module " << ipa->path()
-			<< " forced through environment variable";
-		return false;
-	}
-
 	File file{ ipa->path() };
 	if (!file.open(File::OpenModeFlag::ReadOnly))
 		return false;
@@ -323,4 +362,48 @@  bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
 #endif
 }
 
+bool IPAManager::isTrustedChecksum([[maybe_unused]] IPAModule *ipa) const
+{
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+	File file{ ipa->path() };
+	if (!file.open(File::OpenModeFlag::ReadOnly))
+		return false;
+
+	Span<uint8_t> data = file.map();
+	if (data.empty())
+		return false;
+
+	bool valid = false;
+
+	for (std::vector<uint8_t> t : trusted_checksums_) {
+		if (std::equal(t.begin(), t.begin() + 32,
+			       ipa->checksum().begin())) {
+			valid = true;
+			continue;
+		}
+	}
+
+	LOG(IPAManager, Debug)
+		<< "IPA module " << ipa->path() << " checksum is "
+		<< (valid ? "trusted" : "not trusted");
+
+	return valid;
+#else
+	return false;
+#endif
+}
+
+bool IPAManager::isTrusted(IPAModule *ipa) const
+{
+	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
+	if (force && force[0] != '\0') {
+		LOG(IPAManager, Debug)
+			<< "Isolation of IPA module " << ipa->path()
+			<< " forced through environment variable";
+		return false;
+	}
+	return isTrustedChecksum(ipa) || isSignatureValid(ipa);
+}
+
+
 } /* namespace libcamera */
diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp
index f2dd87e5..3d49432b 100644
--- a/src/libcamera/ipa_module.cpp
+++ b/src/libcamera/ipa_module.cpp
@@ -27,6 +27,12 @@ 
 
 #include "libcamera/internal/pipeline_handler.h"
 
+#if HAVE_CRYPTO
+#include <openssl/sha.h>
+#elif HAVE_GNUTLS
+#include <gnutls/crypto.h>
+#endif
+
 /**
  * \file ipa_module.h
  * \brief Image Processing Algorithm module
@@ -281,6 +287,17 @@  int IPAModule::loadIPAModuleInfo()
 	}
 
 	Span<const uint8_t> data = file.map();
+
+	/* Calculate the module checksum. */
+	uint8_t digest[32] = { 0 };
+#if HAVE_CRYPTO
+	SHA256(data.data(), data.size(), digest);
+#elif HAVE_GNUTLS
+	gnutls_hash_fast(GNUTLS_DIG_SHA256, data.data(), data.size(), digest);
+#endif
+	checksum_ = std::vector<uint8_t>(digest, digest + 32);
+
+	/* Interpret the file. */
 	int ret = elfVerifyIdent(data);
 	if (ret) {
 		LOG(IPAModule, Error) << "IPA module is not an ELF file";
@@ -379,6 +396,19 @@  const std::vector<uint8_t> IPAModule::signature() const
 	return signature_;
 }
 
+/**
+ * \brief Retrieve the IPA module checksum
+ *
+ * The IPA module checksum is loaded when the IPAModule instance is created.
+ *
+ * \return The IPA module checksum
+ */
+const std::vector<uint8_t> IPAModule::checksum() const
+{
+	return checksum_;
+}
+
+
 /**
  * \brief Retrieve the IPA module path
  *
diff --git a/src/meson.build b/src/meson.build
index 165a77bb..6cfc3316 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -14,19 +14,30 @@  summary({
          'LIBCAMERA_SYSCONF_DIR' : config_h.get('LIBCAMERA_SYSCONF_DIR'),
          }, section : 'Paths')
 
+# Trusted module checksumming
+ipa_checksum_trusted_modules = true
+
+if ipa_checksum_trusted_modules
+    config_h.set('HAVE_IPA_TRUSTED_MODULE_CHECKSUMS', 1)
+endif
+
 # Module Signing
-openssl = find_program('openssl', required : false)
-if openssl.found()
+openssl = find_program('openssl', required : get_option('ipa_sign_modules'))
+if get_option('ipa_sign_modules').enabled() and openssl.found()
     ipa_priv_key = custom_target('ipa-priv-key',
                                  output : ['ipa-priv-key.pem'],
                                  command : [gen_ipa_priv_key, '@OUTPUT@'])
     config_h.set('HAVE_IPA_PUBKEY', 1)
     ipa_sign_module = true
 else
-    warning('openssl not found, all IPA modules will be isolated')
     ipa_sign_module = false
 endif
 
+if not ipa_checksum_trusted_modules and not ipa_sign_module
+    warning('neither checksums nor signatures enabled,')
+    warning('all IPA modules will be isolated')
+endif
+
 # libcamera must be built first as a dependency to the other components.
 subdir('libcamera')