diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h
index bf823563..2475e26a 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,7 @@ public:
 		return pubKey_;
 	}
 #endif
+	static void loadTrustedChecksums(std::vector<std::vector<uint8_t>> &checksums);
 
 private:
 	static IPAManager *self_;
@@ -65,6 +66,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 +75,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..dd753ca8 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -30,6 +30,14 @@ 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('ipa_checksum_trusted_modules',
+        type : 'feature',
+        description : 'enable IPA trusted module checksums')
+
 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..facfd481 100644
--- a/src/apps/ipa-verify/main.cpp
+++ b/src/apps/ipa-verify/main.cpp
@@ -18,7 +18,8 @@ using namespace libcamera;
 
 namespace {
 
-bool isSignatureValid(IPAModule *ipa)
+#if HAVE_IPA_PUBKEY
+bool isSignatureValid([[maybe_unused]] IPAModule *ipa)
 {
 	File file{ ipa->path() };
 	if (!file.open(File::OpenModeFlag::ReadOnly))
@@ -30,6 +31,22 @@ bool isSignatureValid(IPAModule *ipa)
 
 	return IPAManager::pubKey().verify(data, ipa->signature());
 }
+#endif
+
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+bool isChecksumTrusted([[maybe_unused]] IPAModule *ipa)
+{
+	std::vector<std::vector<uint8_t>> trusted;
+	IPAManager::loadTrustedChecksums(trusted);
+	for (std::vector<uint8_t> t : trusted) {
+		if (std::equal(t.begin(), t.begin() + 32,
+			       ipa->checksum().begin())) {
+			return true;
+		}
+	}
+	return false;
+}
+#endif
 
 void usage(char *argv0)
 {
@@ -54,11 +71,31 @@ int main(int argc, char **argv)
 		return EXIT_FAILURE;
 	}
 
-	if (!isSignatureValid(&module)) {
+	bool ok = false;
+#if HAVE_IPA_PUBKEY
+	if (isSignatureValid(&module)) {
+		std::cout << "IPA module signature is valid" << std::endl;
+		ok = true;
+	} else {
 		std::cout << "IPA module signature is invalid" << std::endl;
+	}
+#else
+	std::cout << "IPA module signing is disabled" << std::endl;
+#endif
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+	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;
+	}
+#else
+	std::cout << "IPA module checksums are disabled" << std::endl;
+#endif
+
+	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..41a1cb48
--- /dev/null
+++ b/src/ipa/ipa-checksum-install.sh
@@ -0,0 +1,24 @@
+#!/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"
+
+lib_file=$(find ${MESON_INSTALL_DESTDIR_PREFIX}/lib -type f -regex .*/libcamera.so.*)
+
+i=1
+for module in ${modules} ; do
+	module="${MESON_INSTALL_DESTDIR_PREFIX}/${module}"
+	if [ -f "${module}" ] ; then
+        checksum=$(sha256sum -b "${module}" | cut -d " " -f 1)
+        # FIXME support for trusting multiple modules
+        sed -b -e "s/embedded_ipa_trusted_module_checksum_sha256_placeholder_0000000${i}/${checksum}/" -i ${lib_file}
+        i=$((i+1))
+	fi
+done
diff --git a/src/ipa/meson.build b/src/ipa/meson.build
index 48793e07..00299a7c 100644
--- a/src/ipa/meson.build
+++ b/src/ipa/meson.build
@@ -75,3 +75,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..96281758 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -114,6 +114,10 @@ IPAManager::IPAManager()
 		LOG(IPAManager, Warning) << "Public key not valid";
 #endif
 
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+	IPAManager::loadTrustedChecksums(trusted_checksums_);
+#endif
+
 	unsigned int ipaCount = 0;
 
 	/* User-specified paths take precedence. */
@@ -165,6 +169,38 @@ IPAManager::~IPAManager()
 	self_ = nullptr;
 }
 
+/**
+ * \brief Load trusted checksums
+ * \param[out] checksums A vector of checksums as 32-element byte vectors
+ */
+void IPAManager::loadTrustedChecksums([[maybe_unused]] std::vector<std::vector<uint8_t>> &checksums)
+{
+#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS
+	// Since there are legitimate reasons for post-processing the modules
+	// in the install phase, we use static placeholders here and allow
+	// replacing them in the binary in the install phase.
+	std::string embeddedChecksums =
+		"embedded_ipa_trusted_module_checksum_sha256_placeholder_00000001\n"
+		"embedded_ipa_trusted_module_checksum_sha256_placeholder_00000002\n"
+		"embedded_ipa_trusted_module_checksum_sha256_placeholder_00000003\n"
+		"embedded_ipa_trusted_module_checksum_sha256_placeholder_00000004\n"
+		"embedded_ipa_trusted_module_checksum_sha256_placeholder_00000005\n"
+		"embedded_ipa_trusted_module_checksum_sha256_placeholder_00000006\n"
+		"embedded_ipa_trusted_module_checksum_sha256_placeholder_00000007\n";
+
+	char* data = embeddedChecksums.data();
+	const int size = strlen(data);
+	for (int i = 0; i < size; i += 65) {
+		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);
+	}
+#endif
+}
+
 /**
  * \brief Identify shared library objects within a directory
  * \param[in] libDir The directory to search for shared objects
@@ -295,14 +331,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 +351,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..2096ff79 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -14,19 +14,31 @@ summary({
          'LIBCAMERA_SYSCONF_DIR' : config_h.get('LIBCAMERA_SYSCONF_DIR'),
          }, section : 'Paths')
 
+# Trusted module checksumming
+if get_option('ipa_checksum_trusted_modules').enabled() or get_option('ipa_checksum_trusted_modules').auto()
+    ipa_checksum_trusted_modules = true
+    config_h.set('HAVE_IPA_TRUSTED_MODULE_CHECKSUMS', 1)
+else
+    ipa_checksum_trusted_modules = false
+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() or get_option('ipa_checksum_trusted_modules').auto()) 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')
 
