| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2023 Hannes Reinecke, SUSE Labs | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/seq_file.h> | |
| #include <linux/key.h> | |
| #include <linux/key-type.h> | |
| #include <keys/user-type.h> | |
| #include <linux/nvme.h> | |
| #include <linux/nvme-tcp.h> | |
| #include <linux/nvme-keyring.h> | |
| static struct key *nvme_keyring; | |
| key_serial_t nvme_keyring_id(void) | |
| { | |
| return nvme_keyring->serial; | |
| } | |
| EXPORT_SYMBOL_GPL(nvme_keyring_id); | |
| static void nvme_tls_psk_describe(const struct key *key, struct seq_file *m) | |
| { | |
| seq_puts(m, key->description); | |
| seq_printf(m, ": %u", key->datalen); | |
| } | |
| static bool nvme_tls_psk_match(const struct key *key, | |
| const struct key_match_data *match_data) | |
| { | |
| const char *match_id; | |
| size_t match_len; | |
| if (!key->description) { | |
| pr_debug("%s: no key description\n", __func__); | |
| return false; | |
| } | |
| match_len = strlen(key->description); | |
| pr_debug("%s: id %s len %zd\n", __func__, key->description, match_len); | |
| if (!match_data->raw_data) { | |
| pr_debug("%s: no match data\n", __func__); | |
| return false; | |
| } | |
| match_id = match_data->raw_data; | |
| pr_debug("%s: match '%s' '%s' len %zd\n", | |
| __func__, match_id, key->description, match_len); | |
| return !memcmp(key->description, match_id, match_len); | |
| } | |
| static int nvme_tls_psk_match_preparse(struct key_match_data *match_data) | |
| { | |
| match_data->lookup_type = KEYRING_SEARCH_LOOKUP_ITERATE; | |
| match_data->cmp = nvme_tls_psk_match; | |
| return 0; | |
| } | |
| static struct key_type nvme_tls_psk_key_type = { | |
| .name = "psk", | |
| .flags = KEY_TYPE_NET_DOMAIN, | |
| .preparse = user_preparse, | |
| .free_preparse = user_free_preparse, | |
| .match_preparse = nvme_tls_psk_match_preparse, | |
| .instantiate = generic_key_instantiate, | |
| .revoke = user_revoke, | |
| .destroy = user_destroy, | |
| .describe = nvme_tls_psk_describe, | |
| .read = user_read, | |
| }; | |
| static struct key *nvme_tls_psk_lookup(struct key *keyring, | |
| const char *hostnqn, const char *subnqn, | |
| int hmac, bool generated) | |
| { | |
| char *identity; | |
| size_t identity_len = (NVMF_NQN_SIZE) * 2 + 11; | |
| key_ref_t keyref; | |
| key_serial_t keyring_id; | |
| identity = kzalloc(identity_len, GFP_KERNEL); | |
| if (!identity) | |
| return ERR_PTR(-ENOMEM); | |
| snprintf(identity, identity_len, "NVMe0%c%02d %s %s", | |
| generated ? 'G' : 'R', hmac, hostnqn, subnqn); | |
| if (!keyring) | |
| keyring = nvme_keyring; | |
| keyring_id = key_serial(keyring); | |
| pr_debug("keyring %x lookup tls psk '%s'\n", | |
| keyring_id, identity); | |
| keyref = keyring_search(make_key_ref(keyring, true), | |
| &nvme_tls_psk_key_type, | |
| identity, false); | |
| if (IS_ERR(keyref)) { | |
| pr_debug("lookup tls psk '%s' failed, error %ld\n", | |
| identity, PTR_ERR(keyref)); | |
| kfree(identity); | |
| return ERR_PTR(-ENOKEY); | |
| } | |
| kfree(identity); | |
| return key_ref_to_ptr(keyref); | |
| } | |
| /* | |
| * NVMe PSK priority list | |
| * | |
| * 'Retained' PSKs (ie 'generated == false') | |
| * should be preferred to 'generated' PSKs, | |
| * and SHA-384 should be preferred to SHA-256. | |
| */ | |
| static struct nvme_tls_psk_priority_list { | |
| bool generated; | |
| enum nvme_tcp_tls_cipher cipher; | |
| } nvme_tls_psk_prio[] = { | |
| { .generated = false, | |
| .cipher = NVME_TCP_TLS_CIPHER_SHA384, }, | |
| { .generated = false, | |
| .cipher = NVME_TCP_TLS_CIPHER_SHA256, }, | |
| { .generated = true, | |
| .cipher = NVME_TCP_TLS_CIPHER_SHA384, }, | |
| { .generated = true, | |
| .cipher = NVME_TCP_TLS_CIPHER_SHA256, }, | |
| }; | |
| /* | |
| * nvme_tls_psk_default - Return the preferred PSK to use for TLS ClientHello | |
| */ | |
| key_serial_t nvme_tls_psk_default(struct key *keyring, | |
| const char *hostnqn, const char *subnqn) | |
| { | |
| struct key *tls_key; | |
| key_serial_t tls_key_id; | |
| int prio; | |
| for (prio = 0; prio < ARRAY_SIZE(nvme_tls_psk_prio); prio++) { | |
| bool generated = nvme_tls_psk_prio[prio].generated; | |
| enum nvme_tcp_tls_cipher cipher = nvme_tls_psk_prio[prio].cipher; | |
| tls_key = nvme_tls_psk_lookup(keyring, hostnqn, subnqn, | |
| cipher, generated); | |
| if (!IS_ERR(tls_key)) { | |
| tls_key_id = tls_key->serial; | |
| key_put(tls_key); | |
| return tls_key_id; | |
| } | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(nvme_tls_psk_default); | |
| static int __init nvme_keyring_init(void) | |
| { | |
| int err; | |
| nvme_keyring = keyring_alloc(".nvme", | |
| GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, | |
| current_cred(), | |
| (KEY_POS_ALL & ~KEY_POS_SETATTR) | | |
| (KEY_USR_ALL & ~KEY_USR_SETATTR), | |
| KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); | |
| if (IS_ERR(nvme_keyring)) | |
| return PTR_ERR(nvme_keyring); | |
| err = register_key_type(&nvme_tls_psk_key_type); | |
| if (err) { | |
| key_put(nvme_keyring); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static void __exit nvme_keyring_exit(void) | |
| { | |
| unregister_key_type(&nvme_tls_psk_key_type); | |
| key_revoke(nvme_keyring); | |
| key_put(nvme_keyring); | |
| } | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_AUTHOR("Hannes Reinecke <hare@suse.de>"); | |
| MODULE_DESCRIPTION("NVMe Keyring implementation"); | |
| module_init(nvme_keyring_init); | |
| module_exit(nvme_keyring_exit); |