diff options
Diffstat (limited to 'certs')
-rw-r--r-- | certs/.gitignore | 3 | ||||
-rw-r--r-- | certs/Kconfig | 103 | ||||
-rw-r--r-- | certs/Makefile | 129 | ||||
-rw-r--r-- | certs/blacklist.c | 243 | ||||
-rw-r--r-- | certs/blacklist.h | 5 | ||||
-rw-r--r-- | certs/blacklist_hashes.c | 7 | ||||
-rw-r--r-- | certs/blacklist_nohashes.c | 6 | ||||
-rw-r--r-- | certs/common.c | 57 | ||||
-rw-r--r-- | certs/common.h | 9 | ||||
-rw-r--r-- | certs/revocation_certificates.S | 21 | ||||
-rw-r--r-- | certs/system_certificates.S | 37 | ||||
-rw-r--r-- | certs/system_keyring.c | 277 |
12 files changed, 897 insertions, 0 deletions
diff --git a/certs/.gitignore b/certs/.gitignore new file mode 100644 index 000000000..6cbd1f1a5 --- /dev/null +++ b/certs/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +x509_certificate_list +x509_revocation_list diff --git a/certs/Kconfig b/certs/Kconfig new file mode 100644 index 000000000..ab88d2a7f --- /dev/null +++ b/certs/Kconfig @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: GPL-2.0 +menu "Certificates for signature checking" + +config MODULE_SIG_KEY + string "File name or PKCS#11 URI of module signing key" + default "certs/signing_key.pem" + depends on MODULE_SIG + help + Provide the file name of a private key/certificate in PEM format, + or a PKCS#11 URI according to RFC7512. The file should contain, or + the URI should identify, both the certificate and its corresponding + private key. + + If this option is unchanged from its default "certs/signing_key.pem", + then the kernel will automatically generate the private key and + certificate as described in Documentation/admin-guide/module-signing.rst + +config SYSTEM_TRUSTED_KEYRING + bool "Provide system-wide ring of trusted keys" + depends on KEYS + depends on ASYMMETRIC_KEY_TYPE + help + Provide a system keyring to which trusted keys can be added. Keys in + the keyring are considered to be trusted. Keys may be added at will + by the kernel from compiled-in data and from hardware key stores, but + userspace may only add extra keys if those keys can be verified by + keys already in the keyring. + + Keys in this keyring are used by module signature checking. + +config SYSTEM_TRUSTED_KEYS + string "Additional X.509 keys for default system keyring" + depends on SYSTEM_TRUSTED_KEYRING + help + If set, this option should be the filename of a PEM-formatted file + containing trusted X.509 certificates to be included in the default + system keyring. Any certificate used for module signing is implicitly + also trusted. + + NOTE: If you previously provided keys for the system keyring in the + form of DER-encoded *.x509 files in the top-level build directory, + those are no longer used. You will need to set this option instead. + +config SYSTEM_EXTRA_CERTIFICATE + bool "Reserve area for inserting a certificate without recompiling" + depends on SYSTEM_TRUSTED_KEYRING + help + If set, space for an extra certificate will be reserved in the kernel + image. This allows introducing a trusted certificate to the default + system keyring without recompiling the kernel. + +config SYSTEM_EXTRA_CERTIFICATE_SIZE + int "Number of bytes to reserve for the extra certificate" + depends on SYSTEM_EXTRA_CERTIFICATE + default 4096 + help + This is the number of bytes reserved in the kernel image for a + certificate to be inserted. + +config SECONDARY_TRUSTED_KEYRING + bool "Provide a keyring to which extra trustable keys may be added" + depends on SYSTEM_TRUSTED_KEYRING + help + If set, provide a keyring to which extra keys may be added, provided + those keys are not blacklisted and are vouched for by a key built + into the kernel or already in the secondary trusted keyring. + +config SYSTEM_BLACKLIST_KEYRING + bool "Provide system-wide ring of blacklisted keys" + depends on KEYS + help + Provide a system keyring to which blacklisted keys can be added. + Keys in the keyring are considered entirely untrusted. Keys in this + keyring are used by the module signature checking to reject loading + of modules signed with a blacklisted key. + +config SYSTEM_BLACKLIST_HASH_LIST + string "Hashes to be preloaded into the system blacklist keyring" + depends on SYSTEM_BLACKLIST_KEYRING + help + If set, this option should be the filename of a list of hashes in the + form "<hash>", "<hash>", ... . This will be included into a C + wrapper to incorporate the list into the kernel. Each <hash> should + be a string of hex digits. + +config SYSTEM_REVOCATION_LIST + bool "Provide system-wide ring of revocation certificates" + depends on SYSTEM_BLACKLIST_KEYRING + depends on PKCS7_MESSAGE_PARSER=y + help + If set, this allows revocation certificates to be stored in the + blacklist keyring and implements a hook whereby a PKCS#7 message can + be checked to see if it matches such a certificate. + +config SYSTEM_REVOCATION_KEYS + string "X.509 certificates to be preloaded into the system blacklist keyring" + depends on SYSTEM_REVOCATION_LIST + help + If set, this option should be the filename of a PEM-formatted file + containing X.509 certificates to be included in the default blacklist + keyring. + +endmenu diff --git a/certs/Makefile b/certs/Makefile new file mode 100644 index 000000000..b338799c0 --- /dev/null +++ b/certs/Makefile @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the linux kernel signature checking certificates. +# + +obj-$(CONFIG_SYSTEM_TRUSTED_KEYRING) += system_keyring.o system_certificates.o common.o +obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist.o common.o +obj-$(CONFIG_SYSTEM_REVOCATION_LIST) += revocation_certificates.o +ifneq ($(CONFIG_SYSTEM_BLACKLIST_HASH_LIST),"") +obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist_hashes.o +else +obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist_nohashes.o +endif + +ifeq ($(CONFIG_SYSTEM_TRUSTED_KEYRING),y) + +$(eval $(call config_filename,SYSTEM_TRUSTED_KEYS)) + +# GCC doesn't include .incbin files in -MD generated dependencies (PR#66871) +$(obj)/system_certificates.o: $(obj)/x509_certificate_list + +# Cope with signing_key.x509 existing in $(srctree) not $(objtree) +AFLAGS_system_certificates.o := -I$(srctree) + +quiet_cmd_extract_certs = EXTRACT_CERTS $(patsubst "%",%,$(2)) + cmd_extract_certs = scripts/extract-cert $(2) $@ + +targets += x509_certificate_list +$(obj)/x509_certificate_list: scripts/extract-cert $(SYSTEM_TRUSTED_KEYS_SRCPREFIX)$(SYSTEM_TRUSTED_KEYS_FILENAME) FORCE + $(call if_changed,extract_certs,$(SYSTEM_TRUSTED_KEYS_SRCPREFIX)$(CONFIG_SYSTEM_TRUSTED_KEYS)) +endif # CONFIG_SYSTEM_TRUSTED_KEYRING + +clean-files := x509_certificate_list .x509.list x509_revocation_list + +ifeq ($(CONFIG_MODULE_SIG),y) +############################################################################### +# +# If module signing is requested, say by allyesconfig, but a key has not been +# supplied, then one will need to be generated to make sure the build does not +# fail and that the kernel may be used afterwards. +# +############################################################################### +ifndef CONFIG_MODULE_SIG_HASH +$(error Could not determine digest type to use from kernel config) +endif + +redirect_openssl = 2>&1 +quiet_redirect_openssl = 2>&1 +silent_redirect_openssl = 2>/dev/null +openssl_available = $(shell openssl help 2>/dev/null && echo yes) + +# We do it this way rather than having a boolean option for enabling an +# external private key, because 'make randconfig' might enable such a +# boolean option and we unfortunately can't make it depend on !RANDCONFIG. +ifeq ($(CONFIG_MODULE_SIG_KEY),"certs/signing_key.pem") + +ifeq ($(openssl_available),yes) +X509TEXT=$(shell openssl x509 -in "certs/signing_key.pem" -text 2>/dev/null) + +$(if $(findstring rsaEncryption,$(X509TEXT)),,$(shell rm -f "certs/signing_key.pem")) +endif + +$(obj)/signing_key.pem: $(obj)/x509.genkey + @$(kecho) "###" + @$(kecho) "### Now generating an X.509 key pair to be used for signing modules." + @$(kecho) "###" + @$(kecho) "### If this takes a long time, you might wish to run rngd in the" + @$(kecho) "### background to keep the supply of entropy topped up. It" + @$(kecho) "### needs to be run as root, and uses a hardware random" + @$(kecho) "### number generator if one is available." + @$(kecho) "###" + $(Q)openssl req -new -nodes -utf8 -$(CONFIG_MODULE_SIG_HASH) -days 36500 \ + -batch -x509 -config $(obj)/x509.genkey \ + -outform PEM -out $(obj)/signing_key.pem \ + -keyout $(obj)/signing_key.pem \ + $($(quiet)redirect_openssl) + @$(kecho) "###" + @$(kecho) "### Key pair generated." + @$(kecho) "###" + +$(obj)/x509.genkey: + @$(kecho) Generating X.509 key generation config + @echo >$@ "[ req ]" + @echo >>$@ "default_bits = 4096" + @echo >>$@ "distinguished_name = req_distinguished_name" + @echo >>$@ "prompt = no" + @echo >>$@ "string_mask = utf8only" + @echo >>$@ "x509_extensions = myexts" + @echo >>$@ + @echo >>$@ "[ req_distinguished_name ]" + @echo >>$@ "#O = Unspecified company" + @echo >>$@ "CN = Build time autogenerated kernel key" + @echo >>$@ "#emailAddress = unspecified.user@unspecified.company" + @echo >>$@ + @echo >>$@ "[ myexts ]" + @echo >>$@ "basicConstraints=critical,CA:FALSE" + @echo >>$@ "keyUsage=digitalSignature" + @echo >>$@ "subjectKeyIdentifier=hash" + @echo >>$@ "authorityKeyIdentifier=keyid" +endif # CONFIG_MODULE_SIG_KEY + +$(eval $(call config_filename,MODULE_SIG_KEY)) + +# If CONFIG_MODULE_SIG_KEY isn't a PKCS#11 URI, depend on it +ifeq ($(patsubst pkcs11:%,%,$(firstword $(MODULE_SIG_KEY_FILENAME))),$(firstword $(MODULE_SIG_KEY_FILENAME))) +X509_DEP := $(MODULE_SIG_KEY_SRCPREFIX)$(MODULE_SIG_KEY_FILENAME) +endif + +# GCC PR#66871 again. +$(obj)/system_certificates.o: $(obj)/signing_key.x509 + +targets += signing_key.x509 +$(obj)/signing_key.x509: scripts/extract-cert $(X509_DEP) FORCE + $(call if_changed,extract_certs,$(MODULE_SIG_KEY_SRCPREFIX)$(CONFIG_MODULE_SIG_KEY)) +endif # CONFIG_MODULE_SIG + +ifeq ($(CONFIG_SYSTEM_REVOCATION_LIST),y) + +$(eval $(call config_filename,SYSTEM_REVOCATION_KEYS)) + +$(obj)/revocation_certificates.o: $(obj)/x509_revocation_list + +quiet_cmd_extract_certs = EXTRACT_CERTS $(patsubst "%",%,$(2)) + cmd_extract_certs = scripts/extract-cert $(2) $@ + +targets += x509_revocation_list +$(obj)/x509_revocation_list: scripts/extract-cert $(SYSTEM_REVOCATION_KEYS_SRCPREFIX)$(SYSTEM_REVOCATION_KEYS_FILENAME) FORCE + $(call if_changed,extract_certs,$(SYSTEM_REVOCATION_KEYS_SRCPREFIX)$(CONFIG_SYSTEM_REVOCATION_KEYS)) +endif diff --git a/certs/blacklist.c b/certs/blacklist.c new file mode 100644 index 000000000..c973de883 --- /dev/null +++ b/certs/blacklist.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* System hash blacklist. + * + * Copyright (C) 2016 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#define pr_fmt(fmt) "blacklist: "fmt +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/key.h> +#include <linux/key-type.h> +#include <linux/sched.h> +#include <linux/ctype.h> +#include <linux/err.h> +#include <linux/seq_file.h> +#include <keys/system_keyring.h> +#include "blacklist.h" +#include "common.h" + +static struct key *blacklist_keyring; + +#ifdef CONFIG_SYSTEM_REVOCATION_LIST +extern __initconst const u8 revocation_certificate_list[]; +extern __initconst const unsigned long revocation_certificate_list_size; +#endif + +/* + * The description must be a type prefix, a colon and then an even number of + * hex digits. The hash is kept in the description. + */ +static int blacklist_vet_description(const char *desc) +{ + int n = 0; + + if (*desc == ':') + return -EINVAL; + for (; *desc; desc++) + if (*desc == ':') + goto found_colon; + return -EINVAL; + +found_colon: + desc++; + for (; *desc; desc++) { + if (!isxdigit(*desc)) + return -EINVAL; + n++; + } + + if (n == 0 || n & 1) + return -EINVAL; + return 0; +} + +/* + * The hash to be blacklisted is expected to be in the description. There will + * be no payload. + */ +static int blacklist_preparse(struct key_preparsed_payload *prep) +{ + if (prep->datalen > 0) + return -EINVAL; + return 0; +} + +static void blacklist_free_preparse(struct key_preparsed_payload *prep) +{ +} + +static void blacklist_describe(const struct key *key, struct seq_file *m) +{ + seq_puts(m, key->description); +} + +static struct key_type key_type_blacklist = { + .name = "blacklist", + .vet_description = blacklist_vet_description, + .preparse = blacklist_preparse, + .free_preparse = blacklist_free_preparse, + .instantiate = generic_key_instantiate, + .describe = blacklist_describe, +}; + +/** + * mark_hash_blacklisted - Add a hash to the system blacklist + * @hash - The hash as a hex string with a type prefix (eg. "tbs:23aa429783") + */ +int mark_hash_blacklisted(const char *hash) +{ + key_ref_t key; + + key = key_create_or_update(make_key_ref(blacklist_keyring, true), + "blacklist", + hash, + NULL, + 0, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW), + KEY_ALLOC_NOT_IN_QUOTA | + KEY_ALLOC_BUILT_IN); + if (IS_ERR(key)) { + pr_err("Problem blacklisting hash (%ld)\n", PTR_ERR(key)); + return PTR_ERR(key); + } + return 0; +} + +/** + * is_hash_blacklisted - Determine if a hash is blacklisted + * @hash: The hash to be checked as a binary blob + * @hash_len: The length of the binary hash + * @type: Type of hash + */ +int is_hash_blacklisted(const u8 *hash, size_t hash_len, const char *type) +{ + key_ref_t kref; + size_t type_len = strlen(type); + char *buffer, *p; + int ret = 0; + + buffer = kmalloc(type_len + 1 + hash_len * 2 + 1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + p = memcpy(buffer, type, type_len); + p += type_len; + *p++ = ':'; + bin2hex(p, hash, hash_len); + p += hash_len * 2; + *p = 0; + + kref = keyring_search(make_key_ref(blacklist_keyring, true), + &key_type_blacklist, buffer, false); + if (!IS_ERR(kref)) { + key_ref_put(kref); + ret = -EKEYREJECTED; + } + + kfree(buffer); + return ret; +} +EXPORT_SYMBOL_GPL(is_hash_blacklisted); + +int is_binary_blacklisted(const u8 *hash, size_t hash_len) +{ + if (is_hash_blacklisted(hash, hash_len, "bin") == -EKEYREJECTED) + return -EPERM; + + return 0; +} +EXPORT_SYMBOL_GPL(is_binary_blacklisted); + +#ifdef CONFIG_SYSTEM_REVOCATION_LIST +/** + * add_key_to_revocation_list - Add a revocation certificate to the blacklist + * @data: The data blob containing the certificate + * @size: The size of data blob + */ +int add_key_to_revocation_list(const char *data, size_t size) +{ + key_ref_t key; + + key = key_create_or_update(make_key_ref(blacklist_keyring, true), + "asymmetric", + NULL, + data, + size, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW), + KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN); + + if (IS_ERR(key)) { + pr_err("Problem with revocation key (%ld)\n", PTR_ERR(key)); + return PTR_ERR(key); + } + + return 0; +} + +/** + * is_key_on_revocation_list - Determine if the key for a PKCS#7 message is revoked + * @pkcs7: The PKCS#7 message to check + */ +int is_key_on_revocation_list(struct pkcs7_message *pkcs7) +{ + int ret; + + ret = pkcs7_validate_trust(pkcs7, blacklist_keyring); + + if (ret == 0) + return -EKEYREJECTED; + + return -ENOKEY; +} +#endif + +/* + * Initialise the blacklist + */ +static int __init blacklist_init(void) +{ + const char *const *bl; + + if (register_key_type(&key_type_blacklist) < 0) + panic("Can't allocate system blacklist key type\n"); + + blacklist_keyring = + keyring_alloc(".blacklist", + KUIDT_INIT(0), KGIDT_INIT(0), + current_cred(), + (KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ | + KEY_USR_SEARCH, + KEY_ALLOC_NOT_IN_QUOTA | + KEY_ALLOC_SET_KEEP, + NULL, NULL); + if (IS_ERR(blacklist_keyring)) + panic("Can't allocate system blacklist keyring\n"); + + for (bl = blacklist_hashes; *bl; bl++) + if (mark_hash_blacklisted(*bl) < 0) + pr_err("- blacklisting failed\n"); + return 0; +} + +/* + * Must be initialised before we try and load the keys into the keyring. + */ +device_initcall(blacklist_init); + +#ifdef CONFIG_SYSTEM_REVOCATION_LIST +/* + * Load the compiled-in list of revocation X.509 certificates. + */ +static __init int load_revocation_certificate_list(void) +{ + if (revocation_certificate_list_size) + pr_notice("Loading compiled-in revocation X.509 certificates\n"); + + return load_certificate_list(revocation_certificate_list, revocation_certificate_list_size, + blacklist_keyring); +} +late_initcall(load_revocation_certificate_list); +#endif diff --git a/certs/blacklist.h b/certs/blacklist.h new file mode 100644 index 000000000..51b320cf8 --- /dev/null +++ b/certs/blacklist.h @@ -0,0 +1,5 @@ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <crypto/pkcs7.h> + +extern const char __initconst *const blacklist_hashes[]; diff --git a/certs/blacklist_hashes.c b/certs/blacklist_hashes.c new file mode 100644 index 000000000..d5961aa3d --- /dev/null +++ b/certs/blacklist_hashes.c @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "blacklist.h" + +const char __initconst *const blacklist_hashes[] = { +#include CONFIG_SYSTEM_BLACKLIST_HASH_LIST + , NULL +}; diff --git a/certs/blacklist_nohashes.c b/certs/blacklist_nohashes.c new file mode 100644 index 000000000..753b703ef --- /dev/null +++ b/certs/blacklist_nohashes.c @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "blacklist.h" + +const char __initconst *const blacklist_hashes[] = { + NULL +}; diff --git a/certs/common.c b/certs/common.c new file mode 100644 index 000000000..16a220887 --- /dev/null +++ b/certs/common.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/kernel.h> +#include <linux/key.h> +#include "common.h" + +int load_certificate_list(const u8 cert_list[], + const unsigned long list_size, + const struct key *keyring) +{ + key_ref_t key; + const u8 *p, *end; + size_t plen; + + p = cert_list; + end = p + list_size; + while (p < end) { + /* Each cert begins with an ASN.1 SEQUENCE tag and must be more + * than 256 bytes in size. + */ + if (end - p < 4) + goto dodgy_cert; + if (p[0] != 0x30 && + p[1] != 0x82) + goto dodgy_cert; + plen = (p[2] << 8) | p[3]; + plen += 4; + if (plen > end - p) + goto dodgy_cert; + + key = key_create_or_update(make_key_ref(keyring, 1), + "asymmetric", + NULL, + p, + plen, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ), + KEY_ALLOC_NOT_IN_QUOTA | + KEY_ALLOC_BUILT_IN | + KEY_ALLOC_BYPASS_RESTRICTION); + if (IS_ERR(key)) { + pr_err("Problem loading in-kernel X.509 certificate (%ld)\n", + PTR_ERR(key)); + } else { + pr_notice("Loaded X.509 cert '%s'\n", + key_ref_to_ptr(key)->description); + key_ref_put(key); + } + p += plen; + } + + return 0; + +dodgy_cert: + pr_err("Problem parsing in-kernel X.509 certificate list\n"); + return 0; +} diff --git a/certs/common.h b/certs/common.h new file mode 100644 index 000000000..abdb57959 --- /dev/null +++ b/certs/common.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _CERT_COMMON_H +#define _CERT_COMMON_H + +int load_certificate_list(const u8 cert_list[], const unsigned long list_size, + const struct key *keyring); + +#endif diff --git a/certs/revocation_certificates.S b/certs/revocation_certificates.S new file mode 100644 index 000000000..f21aae8a8 --- /dev/null +++ b/certs/revocation_certificates.S @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/export.h> +#include <linux/init.h> + + __INITRODATA + + .align 8 + .globl revocation_certificate_list +revocation_certificate_list: +__revocation_list_start: + .incbin "certs/x509_revocation_list" +__revocation_list_end: + + .align 8 + .globl revocation_certificate_list_size +revocation_certificate_list_size: +#ifdef CONFIG_64BIT + .quad __revocation_list_end - __revocation_list_start +#else + .long __revocation_list_end - __revocation_list_start +#endif diff --git a/certs/system_certificates.S b/certs/system_certificates.S new file mode 100644 index 000000000..8f29058ad --- /dev/null +++ b/certs/system_certificates.S @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/export.h> +#include <linux/init.h> + + __INITRODATA + + .align 8 + .globl system_certificate_list +system_certificate_list: +__cert_list_start: +#ifdef CONFIG_MODULE_SIG + .incbin "certs/signing_key.x509" +#endif + .incbin "certs/x509_certificate_list" +__cert_list_end: + +#ifdef CONFIG_SYSTEM_EXTRA_CERTIFICATE + .globl system_extra_cert + .size system_extra_cert, CONFIG_SYSTEM_EXTRA_CERTIFICATE_SIZE +system_extra_cert: + .fill CONFIG_SYSTEM_EXTRA_CERTIFICATE_SIZE, 1, 0 + + .align 4 + .globl system_extra_cert_used +system_extra_cert_used: + .int 0 + +#endif /* CONFIG_SYSTEM_EXTRA_CERTIFICATE */ + + .align 8 + .globl system_certificate_list_size +system_certificate_list_size: +#ifdef CONFIG_64BIT + .quad __cert_list_end - __cert_list_start +#else + .long __cert_list_end - __cert_list_start +#endif diff --git a/certs/system_keyring.c b/certs/system_keyring.c new file mode 100644 index 000000000..a44a8915c --- /dev/null +++ b/certs/system_keyring.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* System trusted keyring for trusted public keys + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/cred.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/verification.h> +#include <keys/asymmetric-type.h> +#include <keys/system_keyring.h> +#include <crypto/pkcs7.h> +#include "common.h" + +static struct key *builtin_trusted_keys; +#ifdef CONFIG_SECONDARY_TRUSTED_KEYRING +static struct key *secondary_trusted_keys; +#endif +#ifdef CONFIG_INTEGRITY_PLATFORM_KEYRING +static struct key *platform_trusted_keys; +#endif + +extern __initconst const u8 system_certificate_list[]; +extern __initconst const unsigned long system_certificate_list_size; + +/** + * restrict_link_to_builtin_trusted - Restrict keyring addition by built in CA + * + * Restrict the addition of keys into a keyring based on the key-to-be-added + * being vouched for by a key in the built in system keyring. + */ +int restrict_link_by_builtin_trusted(struct key *dest_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *restriction_key) +{ + return restrict_link_by_signature(dest_keyring, type, payload, + builtin_trusted_keys); +} + +#ifdef CONFIG_SECONDARY_TRUSTED_KEYRING +/** + * restrict_link_by_builtin_and_secondary_trusted - Restrict keyring + * addition by both builtin and secondary keyrings + * + * Restrict the addition of keys into a keyring based on the key-to-be-added + * being vouched for by a key in either the built-in or the secondary system + * keyrings. + */ +int restrict_link_by_builtin_and_secondary_trusted( + struct key *dest_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *restrict_key) +{ + /* If we have a secondary trusted keyring, then that contains a link + * through to the builtin keyring and the search will follow that link. + */ + if (type == &key_type_keyring && + dest_keyring == secondary_trusted_keys && + payload == &builtin_trusted_keys->payload) + /* Allow the builtin keyring to be added to the secondary */ + return 0; + + return restrict_link_by_signature(dest_keyring, type, payload, + secondary_trusted_keys); +} + +/** + * Allocate a struct key_restriction for the "builtin and secondary trust" + * keyring. Only for use in system_trusted_keyring_init(). + */ +static __init struct key_restriction *get_builtin_and_secondary_restriction(void) +{ + struct key_restriction *restriction; + + restriction = kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + + if (!restriction) + panic("Can't allocate secondary trusted keyring restriction\n"); + + restriction->check = restrict_link_by_builtin_and_secondary_trusted; + + return restriction; +} +#endif + +/* + * Create the trusted keyrings + */ +static __init int system_trusted_keyring_init(void) +{ + pr_notice("Initialise system trusted keyrings\n"); + + builtin_trusted_keys = + keyring_alloc(".builtin_trusted_keys", + KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH), + KEY_ALLOC_NOT_IN_QUOTA, + NULL, NULL); + if (IS_ERR(builtin_trusted_keys)) + panic("Can't allocate builtin trusted keyring\n"); + +#ifdef CONFIG_SECONDARY_TRUSTED_KEYRING + secondary_trusted_keys = + keyring_alloc(".secondary_trusted_keys", + KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH | + KEY_USR_WRITE), + KEY_ALLOC_NOT_IN_QUOTA, + get_builtin_and_secondary_restriction(), + NULL); + if (IS_ERR(secondary_trusted_keys)) + panic("Can't allocate secondary trusted keyring\n"); + + if (key_link(secondary_trusted_keys, builtin_trusted_keys) < 0) + panic("Can't link trusted keyrings\n"); +#endif + + return 0; +} + +/* + * Must be initialised before we try and load the keys into the keyring. + */ +device_initcall(system_trusted_keyring_init); + +/* + * Load the compiled-in list of X.509 certificates. + */ +static __init int load_system_certificate_list(void) +{ + pr_notice("Loading compiled-in X.509 certificates\n"); + + return load_certificate_list(system_certificate_list, system_certificate_list_size, + builtin_trusted_keys); +} +late_initcall(load_system_certificate_list); + +#ifdef CONFIG_SYSTEM_DATA_VERIFICATION + +/** + * verify_pkcs7_message_sig - Verify a PKCS#7-based signature on system data. + * @data: The data to be verified (NULL if expecting internal data). + * @len: Size of @data. + * @pkcs7: The PKCS#7 message that is the signature. + * @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only, + * (void *)1UL for all trusted keys). + * @usage: The use to which the key is being put. + * @view_content: Callback to gain access to content. + * @ctx: Context for callback. + */ +int verify_pkcs7_message_sig(const void *data, size_t len, + struct pkcs7_message *pkcs7, + struct key *trusted_keys, + enum key_being_used_for usage, + int (*view_content)(void *ctx, + const void *data, size_t len, + size_t asn1hdrlen), + void *ctx) +{ + int ret; + + /* The data should be detached - so we need to supply it. */ + if (data && pkcs7_supply_detached_data(pkcs7, data, len) < 0) { + pr_err("PKCS#7 signature with non-detached data\n"); + ret = -EBADMSG; + goto error; + } + + ret = pkcs7_verify(pkcs7, usage); + if (ret < 0) + goto error; + + if (!trusted_keys) { + trusted_keys = builtin_trusted_keys; + } else if (trusted_keys == VERIFY_USE_SECONDARY_KEYRING) { +#ifdef CONFIG_SECONDARY_TRUSTED_KEYRING + trusted_keys = secondary_trusted_keys; +#else + trusted_keys = builtin_trusted_keys; +#endif + } else if (trusted_keys == VERIFY_USE_PLATFORM_KEYRING) { +#ifdef CONFIG_INTEGRITY_PLATFORM_KEYRING + trusted_keys = platform_trusted_keys; +#else + trusted_keys = NULL; +#endif + if (!trusted_keys) { + ret = -ENOKEY; + pr_devel("PKCS#7 platform keyring is not available\n"); + goto error; + } + + ret = is_key_on_revocation_list(pkcs7); + if (ret != -ENOKEY) { + pr_devel("PKCS#7 platform key is on revocation list\n"); + goto error; + } + } + ret = pkcs7_validate_trust(pkcs7, trusted_keys); + if (ret < 0) { + if (ret == -ENOKEY) + pr_devel("PKCS#7 signature not signed with a trusted key\n"); + goto error; + } + + if (view_content) { + size_t asn1hdrlen; + + ret = pkcs7_get_content_data(pkcs7, &data, &len, &asn1hdrlen); + if (ret < 0) { + if (ret == -ENODATA) + pr_devel("PKCS#7 message does not contain data\n"); + goto error; + } + + ret = view_content(ctx, data, len, asn1hdrlen); + } + +error: + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} + +/** + * verify_pkcs7_signature - Verify a PKCS#7-based signature on system data. + * @data: The data to be verified (NULL if expecting internal data). + * @len: Size of @data. + * @raw_pkcs7: The PKCS#7 message that is the signature. + * @pkcs7_len: The size of @raw_pkcs7. + * @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only, + * (void *)1UL for all trusted keys). + * @usage: The use to which the key is being put. + * @view_content: Callback to gain access to content. + * @ctx: Context for callback. + */ +int verify_pkcs7_signature(const void *data, size_t len, + const void *raw_pkcs7, size_t pkcs7_len, + struct key *trusted_keys, + enum key_being_used_for usage, + int (*view_content)(void *ctx, + const void *data, size_t len, + size_t asn1hdrlen), + void *ctx) +{ + struct pkcs7_message *pkcs7; + int ret; + + pkcs7 = pkcs7_parse_message(raw_pkcs7, pkcs7_len); + if (IS_ERR(pkcs7)) + return PTR_ERR(pkcs7); + + ret = verify_pkcs7_message_sig(data, len, pkcs7, trusted_keys, usage, + view_content, ctx); + + pkcs7_free_message(pkcs7); + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL_GPL(verify_pkcs7_signature); + +#endif /* CONFIG_SYSTEM_DATA_VERIFICATION */ + +#ifdef CONFIG_INTEGRITY_PLATFORM_KEYRING +void __init set_platform_trusted_keys(struct key *keyring) +{ + platform_trusted_keys = keyring; +} +#endif |