| // 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 bool nvme_tls_psk_revoked(struct key *psk) |
| { |
| return test_bit(KEY_FLAG_REVOKED, &psk->flags) || |
| test_bit(KEY_FLAG_INVALIDATED, &psk->flags); |
| } |
| |
| struct key *nvme_tls_key_lookup(key_serial_t key_id) |
| { |
| struct key *key = key_lookup(key_id); |
| |
| if (IS_ERR(key)) { |
| pr_err("key id %08x not found\n", key_id); |
| return key; |
| } |
| if (nvme_tls_psk_revoked(key)) { |
| pr_err("key id %08x revoked\n", key_id); |
| return ERR_PTR(-EKEYREVOKED); |
| } |
| return key; |
| } |
| EXPORT_SYMBOL_GPL(nvme_tls_key_lookup); |
| |
| 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; |
| } |
| if (!match_data->raw_data) { |
| pr_debug("%s: no match data\n", __func__); |
| return false; |
| } |
| match_id = match_data->raw_data; |
| match_len = strlen(match_id); |
| 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, |
| u8 hmac, u8 psk_ver, 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, "NVMe%u%c%02u %s %s", |
| psk_ver, 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, PSKs with hash (psk_ver 1) should be preferred to PSKs without hash |
| * (psk_ver 0), and SHA-384 should be preferred to SHA-256. |
| */ |
| static struct nvme_tls_psk_priority_list { |
| bool generated; |
| u8 psk_ver; |
| enum nvme_tcp_tls_cipher cipher; |
| } nvme_tls_psk_prio[] = { |
| { .generated = false, |
| .psk_ver = 1, |
| .cipher = NVME_TCP_TLS_CIPHER_SHA384, }, |
| { .generated = false, |
| .psk_ver = 1, |
| .cipher = NVME_TCP_TLS_CIPHER_SHA256, }, |
| { .generated = false, |
| .psk_ver = 0, |
| .cipher = NVME_TCP_TLS_CIPHER_SHA384, }, |
| { .generated = false, |
| .psk_ver = 0, |
| .cipher = NVME_TCP_TLS_CIPHER_SHA256, }, |
| { .generated = true, |
| .psk_ver = 1, |
| .cipher = NVME_TCP_TLS_CIPHER_SHA384, }, |
| { .generated = true, |
| .psk_ver = 1, |
| .cipher = NVME_TCP_TLS_CIPHER_SHA256, }, |
| { .generated = true, |
| .psk_ver = 0, |
| .cipher = NVME_TCP_TLS_CIPHER_SHA384, }, |
| { .generated = true, |
| .psk_ver = 0, |
| .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; |
| u8 ver = nvme_tls_psk_prio[prio].psk_ver; |
| enum nvme_tcp_tls_cipher cipher = nvme_tls_psk_prio[prio].cipher; |
| |
| tls_key = nvme_tls_psk_lookup(keyring, hostnqn, subnqn, |
| cipher, ver, 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); |