[RFC PATCH v2 8/8] clavis: Introduce new LSM called clavis

Eric Snowberg eric.snowberg at oracle.com
Fri May 31 00:39:45 UTC 2024


Introduce a new LSM called clavis.  The motivation behind this LSM is to
provide access control for system keys.  The access control list is
contained within a keyring call .clavis.  During boot if the clavis= boot
arg is supplied with a key id contained within any of the current system
keyrings (builtin, secondary, machine, or platform) it shall be used as
the root of trust for validating anything that is added to the ACL list.

The first restriction introduced with this LSM is the ability to enforce
key usage.  The kernel already has a notion of tracking key usage.  This
LSM adds the ability to enforce this usage based on the system owners
configuration.

Each system key may have one or more uses defined within the ACL list.
When this LSM is enabled, only the builtin keys are available for loading
kernel modules and doing a kexec.  Until an entry is added to the .clavis
keyring, no other system key may be used for any other purpose.

In the future it is envisioned this LSM could be enhanced to provide
access control for UEFI Secure Boot Advanced Targeting (SBAT).  Using
the same clavis= boot param and storing the additional contents within
the new Runtime Services UEFI var, SBAT restrictions could be maintained
across kexec.

Signed-off-by: Eric Snowberg <eric.snowberg at oracle.com>
---
 Documentation/admin-guide/LSM/clavis.rst | 198 +++++++++++++++++++++++
 MAINTAINERS                              |   7 +
 crypto/asymmetric_keys/signature.c       |   4 +
 include/linux/lsm_hook_defs.h            |   2 +
 include/linux/security.h                 |   7 +
 include/uapi/linux/lsm.h                 |   1 +
 security/Kconfig                         |  10 +-
 security/clavis/Makefile                 |   1 +
 security/clavis/clavis.c                 |  25 +++
 security/clavis/clavis.h                 |   4 +
 security/clavis/clavis_keyring.c         |  83 ++++++++++
 security/security.c                      |  16 +-
 12 files changed, 352 insertions(+), 6 deletions(-)
 create mode 100644 Documentation/admin-guide/LSM/clavis.rst
 create mode 100644 security/clavis/clavis.c

diff --git a/Documentation/admin-guide/LSM/clavis.rst b/Documentation/admin-guide/LSM/clavis.rst
new file mode 100644
index 000000000000..d1641e3ef38b
--- /dev/null
+++ b/Documentation/admin-guide/LSM/clavis.rst
@@ -0,0 +1,198 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======
+Clavis
+======
+
+Clavis is a Linux Security Module that provides mandatory access control to
+system kernel keys (i.e. builtin, secondary, machine and platform). These
+restrictions will prohibit keys from being used for validation. Upon boot, the
+Clavis LSM is provided a key id as a boot param.  This single key is then
+used as the root of trust for any access control modifications made going
+forward. Access control updates must be signed and validated by this key.
+
+Clavis has its own keyring.  All ACL updates are applied through this keyring.
+The update must be signed by the single root of trust key.
+
+When enabled, all system keys are prohibited from being used until an ACL is
+added for them. There is two exceptions to this rule, builtin keys may be used
+to validate both signed kernels and modules.
+
+Adding system kernel keys can only be performed by the machine owner; this
+could be through the Machine Owner Key (MOK) or the UEFI Secure Boot DB. It
+is possible the machine owner and system administrator may be different
+people. The system administrator will not be able to make ACL updates without
+them being signed by the machine owner.
+
+On UEFI platforms, the root of trust key shall survive a kexec. Trying to
+defeat or change it from the command line is not allowed.  The original boot
+param is stored in UEFI and will always be referenced following a kexec.
+
+The Clavis LSM contains a system keyring call .clavis.  It contains a single
+asymmetric key that is use to validate anything added to it.  This key can only
+be added during boot and must be a preexisting system kernel key.  If the
+``clavis=`` boot param is not used, the keyring does not exist and the feature
+can not be used until the next reboot.
+
+The only user space components are OpenSSL and the keyctl utility. A new
+key type call ``clavis_key_acl`` is used for ACL updates. Any number of signed
+``clavis_key_acl`` entries may be added to the .clavis keyring. The
+``clavis_key_acl`` contains the subject key identifier along with the allowed
+usage type for
+the key.
+
+The format is as follows:
+
+.. code-block:: console
+
+  XX:YYYYYYYYYYY
+
+  XX - Single byte of the key type
+	VERIFYING_MODULE_SIGNATURE            00
+	VERIFYING_FIRMWARE_SIGNATURE          01
+	VERIFYING_KEXEC_PE_SIGNATURE          02
+	VERIFYING_KEY_SIGNATURE               03
+	VERIFYING_KEY_SELF_SIGNATURE          04
+	VERIFYING_UNSPECIFIED_SIGNATURE       05
+  :  - ASCII colon
+  YY - Even number of hexadecimal characters representing the key id
+
+The ``clavis_key_acl`` must be S/MIME signed by the sole asymmetric key contained
+within the .clavis keyring.
+
+In the future if new features are added, new key types could be created.
+
+Usage Examples
+==============
+
+How to create a signing key:
+----------------------------
+
+.. code-block:: bash
+
+  cat <<EOF > clavis-lsm.genkey
+  [ req ]
+  default_bits = 4096
+  distinguished_name = req_distinguished_name
+  prompt = no
+  string_mask = utf8only
+  x509_extensions = v3_ca
+  [ req_distinguished_name ]
+  O = TEST
+  CN = Clavis LSM key
+  emailAddress = user at example.com
+  [ v3_ca ]
+  basicConstraints=CA:TRUE
+  subjectKeyIdentifier=hash
+  authorityKeyIdentifier=keyid:always,issuer
+  keyUsage=digitalSignature
+  EOF
+
+  openssl req -new -x509 -utf8 -sha256 -days 3650 -batch \
+        -config clavis-lsm.genkey -outform DER \
+        -out clavis-lsm.x509 -keyout clavis-lsm.priv
+
+How to get the Subject Key Identifier
+-------------------------------------
+
+.. code-block:: bash
+
+  openssl x509 -in ./clavis-lsm.x509 -inform der \
+        -ext subjectKeyIdentifier  -nocert \
+        | tail -n +2 | cut -f2 -d '='| tr -d ':'
+  4a00ab9f35c9dc3aed7c225d22bafcbd9285e1e8
+
+How to enroll the signing key into the MOK
+------------------------------------------
+
+The key must now be added to the machine or platform keyrings.  This
+indicates the key was added by the system owner. To add to the machine
+keyring on x86 do:
+
+.. code-block:: bash
+
+  mokutil --import ./clavis-lsm.x509
+
+and then reboot and enroll the key through the MokManager.
+
+How to enable the Clavis LSM
+----------------------------
+
+Add the key id to the ``clavis=`` boot param.  With the example above the
+key id is the subject key identifier: 4a00ab9f35c9dc3aed7c225d22bafcbd9285e1e8
+
+Add the following boot param:
+
+.. code-block:: console
+
+  clavis=4a00ab9f35c9dc3aed7c225d22bafcbd9285e1e8
+
+After booting there will be a single key contained in the .clavis keyring:
+
+.. code-block:: bash
+
+  keyctl show %:.clavis
+  Keyring
+    254954913 ----swrv      0     0  keyring: .clavis
+    301905375 ---lswrv      0     0   \_ asymmetric: TEST: Clavis LSM key: 4a00ab9f35c9dc3aed7c225d22bafcbd9285e1e8
+
+The original ``clavis=`` boot param will persist across any kexec. Changing it or
+removing it has no effect.
+
+
+How to sign an entry to be added to the .clavis keyring:
+--------------------------------------------------------
+
+In this example we have 3 keys in the machine keyring.  Our Clavis LSM key, a
+key we want to use for kernel verification and a key we want to use for module
+verification.
+
+.. code-block:: bash
+
+  keyctl show %:.machine
+  Keyring
+    999488265 ---lswrv      0     0  keyring: .machine
+    912608009 ---lswrv      0     0   \_ asymmetric: TEST: Module Key: 17eb8c5bf766364be094c577625213700add9471
+    646229664 ---lswrv      0     0   \_ asymmetric: TEST: Kernel Key: b360d113c848ace3f1e6a80060b43d1206f0487d
+   1073737099 ---lswrv      0     0   \_ asymmetric: TEST: Clavis LSM key: 4a00ab9f35c9dc3aed7c225d22bafcbd9285e1e8
+
+To update the .clavis kerying ACL list.  First create a file containing the
+key usage type followed by a colon and the key id that we want to allow to
+validate that usage.  In the first example we are saying key
+17eb8c5bf766364be094c577625213700add9471 is allowed to validate kernel modules.
+In the second example we are saying key b360d113c848ace3f1e6a80060b43d1206f0487d
+is allowed to validate signed kernels.
+
+.. code-block:: bash
+
+  echo "00:17eb8c5bf766364be094c577625213700add9471" > module-acl.txt
+  echo "02:b360d113c848ace3f1e6a80060b43d1206f0487d" > kernel-acl.txt
+
+Now both these files must be signed by the key contained in the .clavis keyring:
+
+.. code-block:: bash
+
+  openssl smime -sign -signer clavis-lsm.x509 -inkey clavis-lsm.priv -in module-acl.txt \
+        -out module-acl.pkcs7 -binary -outform DER -nodetach -noattr
+
+  openssl smime -sign -signer clavis-lsm.x509 -inkey clavis-lsm.priv -in kernel-acl.txt \
+        -out kernel-acl.pkcs7 -binary -outform DER -nodetach -noattr
+
+Afterwards the ACL list in the clavis keyring can be updated:
+
+.. code-block:: bash
+
+  keyctl padd clavis_key_acl "" %:.clavis < module-acl.pkcs7
+  keyctl padd clavis_key_acl "" %:.clavis < kernel-acl.pkcs7
+
+  keyctl show %:.clavis
+
+  Keyring
+    254954913 ----swrv      0     0  keyring: .clavis
+    301905375 ---lswrv      0     0   \_ asymmetric: TEST: Clavis LSM key: 4a00ab9f35c9dc3aed7c225d22bafcbd9285e1e8
+   1013065475 --alswrv      0     0   \_ clavis_key_acl: 02:b360d113c848ace3f1e6a80060b43d1206f0487d
+    445581284 --alswrv      0     0   \_ clavis_key_acl: 00:17eb8c5bf766364be094c577625213700add9471
+
+Now the 17eb8c5bf766364be094c577625213700add9471 key can be used for
+validating kernel modules and the b360d113c848ace3f1e6a80060b43d1206f0487d
+key can be used to validate signed kernels.
diff --git a/MAINTAINERS b/MAINTAINERS
index d6c90161c7bf..edf28dee71f2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5326,6 +5326,13 @@ F:	scripts/Makefile.clang
 F:	scripts/clang-tools/
 K:	\b(?i:clang|llvm)\b
 
+CLAVIS LINUX SECURITY MODULE
+M:	Eric Snowberg <eric.snowberg at oracle.com>
+L:	linux-security-module at vger.kernel.org
+S:	Maintained
+F:	Documentation/admin-guide/LSM/clavis.rst
+F:	security/clavis
+
 CLK API
 M:	Russell King <linux at armlinux.org.uk>
 L:	linux-clk at vger.kernel.org
diff --git a/crypto/asymmetric_keys/signature.c b/crypto/asymmetric_keys/signature.c
index 2deff81f8af5..7e3a78650a93 100644
--- a/crypto/asymmetric_keys/signature.c
+++ b/crypto/asymmetric_keys/signature.c
@@ -13,6 +13,7 @@
 #include <linux/err.h>
 #include <linux/slab.h>
 #include <linux/keyctl.h>
+#include <linux/security.h>
 #include <crypto/public_key.h>
 #include <keys/user-type.h>
 #include "asymmetric_keys.h"
@@ -153,6 +154,9 @@ int verify_signature(const struct key *key,
 
 	ret = subtype->verify_signature(key, sig);
 
+	if (!ret)
+		ret = security_key_verify_signature(key, sig);
+
 	pr_devel("<==%s() = %d\n", __func__, ret);
 	return ret;
 }
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index f804b76cde44..6534af90d8db 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -409,6 +409,8 @@ LSM_HOOK(int, 0, key_getsecurity, struct key *key, char **buffer)
 LSM_HOOK(void, LSM_RET_VOID, key_post_create_or_update, struct key *keyring,
 	 struct key *key, const void *payload, size_t payload_len,
 	 unsigned long flags, bool create)
+LSM_HOOK(int, 0, key_verify_signature, const struct key *key,
+	 const struct public_key_signature *sig)
 #endif /* CONFIG_KEYS */
 
 #ifdef CONFIG_AUDIT
diff --git a/include/linux/security.h b/include/linux/security.h
index 21cf70346b33..c5474e9260e0 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -63,6 +63,7 @@ enum fs_value_type;
 struct watch;
 struct watch_notification;
 struct lsm_ctx;
+struct public_key_signature;
 
 /* Default (no) options for the capable function */
 #define CAP_OPT_NONE 0x0
@@ -2009,6 +2010,7 @@ void security_key_post_create_or_update(struct key *keyring, struct key *key,
 					const void *payload, size_t payload_len,
 					unsigned long flags, bool create);
 
+int security_key_verify_signature(const struct key *key, const struct public_key_signature *sig);
 #else
 
 static inline int security_key_alloc(struct key *key,
@@ -2043,6 +2045,11 @@ static inline void security_key_post_create_or_update(struct key *keyring,
 						      bool create)
 { }
 
+static inline int security_key_verify_signature(const struct key *key,
+						const struct public_key_signature *sig)
+{
+	return 0;
+}
 #endif
 #endif /* CONFIG_KEYS */
 
diff --git a/include/uapi/linux/lsm.h b/include/uapi/linux/lsm.h
index 33d8c9f4aa6b..3a60c4ff5186 100644
--- a/include/uapi/linux/lsm.h
+++ b/include/uapi/linux/lsm.h
@@ -64,6 +64,7 @@ struct lsm_ctx {
 #define LSM_ID_LANDLOCK		110
 #define LSM_ID_IMA		111
 #define LSM_ID_EVM		112
+#define LSM_ID_CLAVIS		113
 
 /*
  * LSM_ATTR_XXX definitions identify different LSM attributes
diff --git a/security/Kconfig b/security/Kconfig
index b9ad8e580b96..7df8b2a4941f 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -232,11 +232,11 @@ endchoice
 
 config LSM
 	string "Ordered list of enabled LSMs"
-	default "landlock,lockdown,yama,loadpin,safesetid,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
-	default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
-	default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
-	default "landlock,lockdown,yama,loadpin,safesetid,bpf" if DEFAULT_SECURITY_DAC
-	default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,bpf"
+	default "clavis,landlock,lockdown,yama,loadpin,safesetid,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
+	default "clavis,landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
+	default "clavis,landlock,lockdown,yama,loadpin,safesetid,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
+	default "clavis,landlock,lockdown,yama,loadpin,safesetid,bpf" if DEFAULT_SECURITY_DAC
+	default "clavis,landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,bpf"
 	help
 	  A comma-separated list of LSMs, in initialization order.
 	  Any LSMs left off this list, except for those with order
diff --git a/security/clavis/Makefile b/security/clavis/Makefile
index 2b2b3bc8eef4..441c70c6b78a 100644
--- a/security/clavis/Makefile
+++ b/security/clavis/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 obj-$(CONFIG_SECURITY_CLAVIS) += clavis_keyring.o
+obj-$(CONFIG_SECURITY_CLAVIS) += clavis.o
 ifeq ($(CONFIG_EFI),y)
 obj-$(CONFIG_SECURITY_CLAVIS) += clavis_efi.o
 endif
diff --git a/security/clavis/clavis.c b/security/clavis/clavis.c
new file mode 100644
index 000000000000..040337dbd8d9
--- /dev/null
+++ b/security/clavis/clavis.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+#include <linux/lsm_hooks.h>
+#include <uapi/linux/lsm.h>
+#include "clavis.h"
+
+static struct security_hook_list clavis_hooks[] __ro_after_init = {
+	LSM_HOOK_INIT(key_verify_signature, clavis_sig_verify),
+};
+
+const struct lsm_id clavis_lsmid = {
+	.name = "clavis",
+	.id = LSM_ID_CLAVIS,
+};
+
+static int __init clavis_lsm_init(void)
+{
+	security_add_hooks(clavis_hooks, ARRAY_SIZE(clavis_hooks), &clavis_lsmid);
+	return 0;
+};
+
+DEFINE_LSM(clavis) = {
+	.name = "clavis",
+	.init = clavis_lsm_init,
+};
diff --git a/security/clavis/clavis.h b/security/clavis/clavis.h
index 708dd0b1cc76..2a2fe2525c7c 100644
--- a/security/clavis/clavis.h
+++ b/security/clavis/clavis.h
@@ -2,6 +2,8 @@
 #ifndef _SECURITY_CLAVIS_H_
 #define _SECURITY_CLAVIS_H_
 
+struct key;
+struct public_key_signature;
 struct asymmetric_key_id;
 
 #ifdef CONFIG_EFI
@@ -13,4 +15,6 @@ static inline int __init clavis_efi_param(struct asymmetric_key_id *kid, int len
 }
 #endif
 
+int clavis_sig_verify(const struct key *key, const struct public_key_signature *sig);
+
 #endif /* _SECURITY_CLAVIS_H_ */
diff --git a/security/clavis/clavis_keyring.c b/security/clavis/clavis_keyring.c
index 9b3db299acef..736bdadd9000 100644
--- a/security/clavis/clavis_keyring.c
+++ b/security/clavis/clavis_keyring.c
@@ -13,6 +13,7 @@
 static struct key *clavis_keyring;
 static struct asymmetric_key_id *setup_keyid;
 
+static int clavis_init;
 #define MAX_ASCII_KID 64
 #define MAX_BIN_KID   32
 
@@ -228,4 +229,86 @@ void __init late_init_clavis_setup(void)
 
 	clavis_keyring_init();
 	system_key_link(clavis_keyring, keyid);
+	clavis_init = true;
+}
+
+int clavis_sig_verify(const struct key *key, const struct public_key_signature *sig)
+{
+	const struct asymmetric_key_ids *kids = asymmetric_key_ids(key);
+	const struct asymmetric_key_subtype *subtype;
+	const struct asymmetric_key_id *newkid;
+	char *buf_ptr, *ptr;
+	key_ref_t ref;
+	int i, buf_len;
+
+	if (!clavis_init)
+		return 0;
+
+	if (key->type != &key_type_asymmetric)
+		return -EKEYREJECTED;
+	subtype = asymmetric_key_subtype(key);
+	if (!subtype || !key->payload.data[0])
+		return -EKEYREJECTED;
+	if (!subtype->verify_signature)
+		return -EKEYREJECTED;
+
+	/* Allow sig validation when not using a system keyring */
+	if (!test_bit(PKS_USAGE_SET, &sig->usage_flags))
+		return 0;
+
+	if (test_bit(KEY_FLAG_BUILTIN, &key->flags) && sig->usage == VERIFYING_MODULE_SIGNATURE)
+		return 0;
+
+	if (test_bit(KEY_FLAG_BUILTIN, &key->flags) && sig->usage == VERIFYING_KEXEC_PE_SIGNATURE)
+		return 0;
+
+	/* The previous sig validation is enough to get on the clavis keyring */
+	if (sig->usage == VERIFYING_CLAVIS_SIGNATURE)
+		return 0;
+
+	if (test_bit(PKS_REVOCATION_PASS, &sig->usage_flags))
+		return 0;
+
+	for (i = 0, buf_len = 0; i < 3; i++) {
+		if (kids->id[i]) {
+			newkid = (struct asymmetric_key_id *)kids->id[i];
+			if (newkid->len > buf_len)
+				buf_len = newkid->len;
+		}
+	}
+
+	if (!buf_len)
+		return -EKEYREJECTED;
+
+	/* Allocate enough space for the conversion to ascii plus the header. */
+	buf_ptr = kmalloc(buf_len * 2 + 4, GFP_KERNEL | __GFP_ZERO);
+
+	if (!buf_ptr)
+		return -ENOMEM;
+
+	for (i = 0; i < 3; i++) {
+		if (kids->id[i]) {
+			newkid = (struct asymmetric_key_id *)kids->id[i];
+			if (!newkid->len)
+				continue;
+
+			ptr = buf_ptr;
+			ptr = bin2hex(ptr, &sig->usage, 1);
+			*ptr++ = ':';
+			ptr = bin2hex(ptr, newkid->data, newkid->len);
+			*ptr = 0;
+			ref = keyring_search(make_key_ref(clavis_keyring, true), &clavis_key_acl,
+					     buf_ptr, false);
+
+			if (!IS_ERR(ref))
+				break;
+		}
+	}
+
+	kfree(buf_ptr);
+
+	if (IS_ERR(ref))
+		return -EKEYREJECTED;
+
+	return 0;
 }
diff --git a/security/security.c b/security/security.c
index e5da848c50b9..bd2e13a8f01b 100644
--- a/security/security.c
+++ b/security/security.c
@@ -51,7 +51,8 @@
 	(IS_ENABLED(CONFIG_BPF_LSM) ? 1 : 0) + \
 	(IS_ENABLED(CONFIG_SECURITY_LANDLOCK) ? 1 : 0) + \
 	(IS_ENABLED(CONFIG_IMA) ? 1 : 0) + \
-	(IS_ENABLED(CONFIG_EVM) ? 1 : 0))
+	(IS_ENABLED(CONFIG_EVM) ? 1 : 0) + \
+	(IS_ENABLED(CONFIG_SECURITY_CLAVIS) ? 1 : 0))
 
 /*
  * These are descriptions of the reasons that can be passed to the
@@ -5323,6 +5324,19 @@ void security_key_post_create_or_update(struct key *keyring, struct key *key,
 	call_void_hook(key_post_create_or_update, keyring, key, payload,
 		       payload_len, flags, create);
 }
+
+/**
+ * security_key_verify_signature - verify signature
+ * @key: key
+ * @sig: signature
+ *
+ * See whether signature verification is allowed based on the ACL for
+ * key usage.
+ */
+int security_key_verify_signature(const struct key *key, const struct public_key_signature *sig)
+{
+	return call_int_hook(key_verify_signature, key, sig);
+}
 #endif	/* CONFIG_KEYS */
 
 #ifdef CONFIG_AUDIT
-- 
2.43.0




More information about the Linux-security-module-archive mailing list