Skip to content

Commit f50fff7

Browse files
hreineckeaxboe
authored andcommitted
nvme: implement In-Band authentication
Implement NVMe-oF In-Band authentication according to NVMe TPAR 8006. This patch adds two new fabric options 'dhchap_secret' to specify the pre-shared key (in ASCII respresentation according to NVMe 2.0 section 8.13.5.8 'Secret representation') and 'dhchap_ctrl_secret' to specify the pre-shared controller key for bi-directional authentication of both the host and the controller. Re-authentication can be triggered by writing the PSK into the new controller sysfs attribute 'dhchap_secret' or 'dhchap_ctrl_secret'. Signed-off-by: Hannes Reinecke <hare@suse.de> Reviewed-by: Sagi Grimberg <sagi@grimberg.me> Signed-off-by: Christoph Hellwig <hch@lst.de> [axboe: fold in clang build fix] Signed-off-by: Jens Axboe <axboe@kernel.dk>
1 parent 3bf2fde commit f50fff7

File tree

16 files changed

+1498
-7
lines changed

16 files changed

+1498
-7
lines changed

drivers/nvme/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-License-Identifier: GPL-2.0-only
22
menu "NVME Support"
33

4+
source "drivers/nvme/common/Kconfig"
45
source "drivers/nvme/host/Kconfig"
56
source "drivers/nvme/target/Kconfig"
67

drivers/nvme/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# SPDX-License-Identifier: GPL-2.0-only
22

3+
obj-$(CONFIG_NVME_COMMON) += common/
34
obj-y += host/
45
obj-y += target/

drivers/nvme/common/Kconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
3+
config NVME_COMMON
4+
tristate

drivers/nvme/common/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
3+
ccflags-y += -I$(src)
4+
5+
obj-$(CONFIG_NVME_COMMON) += nvme-common.o
6+
7+
nvme-common-y += auth.o

drivers/nvme/common/auth.c

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (c) 2020 Hannes Reinecke, SUSE Linux
4+
*/
5+
6+
#include <linux/module.h>
7+
#include <linux/crc32.h>
8+
#include <linux/base64.h>
9+
#include <linux/prandom.h>
10+
#include <linux/scatterlist.h>
11+
#include <asm/unaligned.h>
12+
#include <crypto/hash.h>
13+
#include <crypto/dh.h>
14+
#include <linux/nvme.h>
15+
#include <linux/nvme-auth.h>
16+
17+
static u32 nvme_dhchap_seqnum;
18+
static DEFINE_MUTEX(nvme_dhchap_mutex);
19+
20+
u32 nvme_auth_get_seqnum(void)
21+
{
22+
u32 seqnum;
23+
24+
mutex_lock(&nvme_dhchap_mutex);
25+
if (!nvme_dhchap_seqnum)
26+
nvme_dhchap_seqnum = prandom_u32();
27+
else {
28+
nvme_dhchap_seqnum++;
29+
if (!nvme_dhchap_seqnum)
30+
nvme_dhchap_seqnum++;
31+
}
32+
seqnum = nvme_dhchap_seqnum;
33+
mutex_unlock(&nvme_dhchap_mutex);
34+
return seqnum;
35+
}
36+
EXPORT_SYMBOL_GPL(nvme_auth_get_seqnum);
37+
38+
static struct nvme_auth_dhgroup_map {
39+
const char name[16];
40+
const char kpp[16];
41+
} dhgroup_map[] = {
42+
[NVME_AUTH_DHGROUP_NULL] = {
43+
.name = "null", .kpp = "null" },
44+
[NVME_AUTH_DHGROUP_2048] = {
45+
.name = "ffdhe2048", .kpp = "ffdhe2048(dh)" },
46+
[NVME_AUTH_DHGROUP_3072] = {
47+
.name = "ffdhe3072", .kpp = "ffdhe3072(dh)" },
48+
[NVME_AUTH_DHGROUP_4096] = {
49+
.name = "ffdhe4096", .kpp = "ffdhe4096(dh)" },
50+
[NVME_AUTH_DHGROUP_6144] = {
51+
.name = "ffdhe6144", .kpp = "ffdhe6144(dh)" },
52+
[NVME_AUTH_DHGROUP_8192] = {
53+
.name = "ffdhe8192", .kpp = "ffdhe8192(dh)" },
54+
};
55+
56+
const char *nvme_auth_dhgroup_name(u8 dhgroup_id)
57+
{
58+
if (dhgroup_id > ARRAY_SIZE(dhgroup_map))
59+
return NULL;
60+
return dhgroup_map[dhgroup_id].name;
61+
}
62+
EXPORT_SYMBOL_GPL(nvme_auth_dhgroup_name);
63+
64+
const char *nvme_auth_dhgroup_kpp(u8 dhgroup_id)
65+
{
66+
if (dhgroup_id > ARRAY_SIZE(dhgroup_map))
67+
return NULL;
68+
return dhgroup_map[dhgroup_id].kpp;
69+
}
70+
EXPORT_SYMBOL_GPL(nvme_auth_dhgroup_kpp);
71+
72+
u8 nvme_auth_dhgroup_id(const char *dhgroup_name)
73+
{
74+
int i;
75+
76+
if (!dhgroup_name || !strlen(dhgroup_name))
77+
return NVME_AUTH_DHGROUP_INVALID;
78+
for (i = 0; i < ARRAY_SIZE(dhgroup_map); i++) {
79+
if (!strlen(dhgroup_map[i].name))
80+
continue;
81+
if (!strncmp(dhgroup_map[i].name, dhgroup_name,
82+
strlen(dhgroup_map[i].name)))
83+
return i;
84+
}
85+
return NVME_AUTH_DHGROUP_INVALID;
86+
}
87+
EXPORT_SYMBOL_GPL(nvme_auth_dhgroup_id);
88+
89+
static struct nvme_dhchap_hash_map {
90+
int len;
91+
const char hmac[15];
92+
const char digest[8];
93+
} hash_map[] = {
94+
[NVME_AUTH_HASH_SHA256] = {
95+
.len = 32,
96+
.hmac = "hmac(sha256)",
97+
.digest = "sha256",
98+
},
99+
[NVME_AUTH_HASH_SHA384] = {
100+
.len = 48,
101+
.hmac = "hmac(sha384)",
102+
.digest = "sha384",
103+
},
104+
[NVME_AUTH_HASH_SHA512] = {
105+
.len = 64,
106+
.hmac = "hmac(sha512)",
107+
.digest = "sha512",
108+
},
109+
};
110+
111+
const char *nvme_auth_hmac_name(u8 hmac_id)
112+
{
113+
if (hmac_id > ARRAY_SIZE(hash_map))
114+
return NULL;
115+
return hash_map[hmac_id].hmac;
116+
}
117+
EXPORT_SYMBOL_GPL(nvme_auth_hmac_name);
118+
119+
const char *nvme_auth_digest_name(u8 hmac_id)
120+
{
121+
if (hmac_id > ARRAY_SIZE(hash_map))
122+
return NULL;
123+
return hash_map[hmac_id].digest;
124+
}
125+
EXPORT_SYMBOL_GPL(nvme_auth_digest_name);
126+
127+
u8 nvme_auth_hmac_id(const char *hmac_name)
128+
{
129+
int i;
130+
131+
if (!hmac_name || !strlen(hmac_name))
132+
return NVME_AUTH_HASH_INVALID;
133+
134+
for (i = 0; i < ARRAY_SIZE(hash_map); i++) {
135+
if (!strlen(hash_map[i].hmac))
136+
continue;
137+
if (!strncmp(hash_map[i].hmac, hmac_name,
138+
strlen(hash_map[i].hmac)))
139+
return i;
140+
}
141+
return NVME_AUTH_HASH_INVALID;
142+
}
143+
EXPORT_SYMBOL_GPL(nvme_auth_hmac_id);
144+
145+
size_t nvme_auth_hmac_hash_len(u8 hmac_id)
146+
{
147+
if (hmac_id > ARRAY_SIZE(hash_map))
148+
return 0;
149+
return hash_map[hmac_id].len;
150+
}
151+
EXPORT_SYMBOL_GPL(nvme_auth_hmac_hash_len);
152+
153+
struct nvme_dhchap_key *nvme_auth_extract_key(unsigned char *secret,
154+
u8 key_hash)
155+
{
156+
struct nvme_dhchap_key *key;
157+
unsigned char *p;
158+
u32 crc;
159+
int ret, key_len;
160+
size_t allocated_len = strlen(secret);
161+
162+
/* Secret might be affixed with a ':' */
163+
p = strrchr(secret, ':');
164+
if (p)
165+
allocated_len = p - secret;
166+
key = kzalloc(sizeof(*key), GFP_KERNEL);
167+
if (!key)
168+
return ERR_PTR(-ENOMEM);
169+
key->key = kzalloc(allocated_len, GFP_KERNEL);
170+
if (!key->key) {
171+
ret = -ENOMEM;
172+
goto out_free_key;
173+
}
174+
175+
key_len = base64_decode(secret, allocated_len, key->key);
176+
if (key_len < 0) {
177+
pr_debug("base64 key decoding error %d\n",
178+
key_len);
179+
ret = key_len;
180+
goto out_free_secret;
181+
}
182+
183+
if (key_len != 36 && key_len != 52 &&
184+
key_len != 68) {
185+
pr_err("Invalid key len %d\n", key_len);
186+
ret = -EINVAL;
187+
goto out_free_secret;
188+
}
189+
190+
if (key_hash > 0 &&
191+
(key_len - 4) != nvme_auth_hmac_hash_len(key_hash)) {
192+
pr_err("Mismatched key len %d for %s\n", key_len,
193+
nvme_auth_hmac_name(key_hash));
194+
ret = -EINVAL;
195+
goto out_free_secret;
196+
}
197+
198+
/* The last four bytes is the CRC in little-endian format */
199+
key_len -= 4;
200+
/*
201+
* The linux implementation doesn't do pre- and post-increments,
202+
* so we have to do it manually.
203+
*/
204+
crc = ~crc32(~0, key->key, key_len);
205+
206+
if (get_unaligned_le32(key->key + key_len) != crc) {
207+
pr_err("key crc mismatch (key %08x, crc %08x)\n",
208+
get_unaligned_le32(key->key + key_len), crc);
209+
ret = -EKEYREJECTED;
210+
goto out_free_secret;
211+
}
212+
key->len = key_len;
213+
key->hash = key_hash;
214+
return key;
215+
out_free_secret:
216+
kfree_sensitive(key->key);
217+
out_free_key:
218+
kfree(key);
219+
return ERR_PTR(ret);
220+
}
221+
EXPORT_SYMBOL_GPL(nvme_auth_extract_key);
222+
223+
void nvme_auth_free_key(struct nvme_dhchap_key *key)
224+
{
225+
if (!key)
226+
return;
227+
kfree_sensitive(key->key);
228+
kfree(key);
229+
}
230+
EXPORT_SYMBOL_GPL(nvme_auth_free_key);
231+
232+
u8 *nvme_auth_transform_key(struct nvme_dhchap_key *key, char *nqn)
233+
{
234+
const char *hmac_name;
235+
struct crypto_shash *key_tfm;
236+
struct shash_desc *shash;
237+
u8 *transformed_key;
238+
int ret;
239+
240+
if (!key || !key->key) {
241+
pr_warn("No key specified\n");
242+
return ERR_PTR(-ENOKEY);
243+
}
244+
if (key->hash == 0) {
245+
transformed_key = kmemdup(key->key, key->len, GFP_KERNEL);
246+
return transformed_key ? transformed_key : ERR_PTR(-ENOMEM);
247+
}
248+
hmac_name = nvme_auth_hmac_name(key->hash);
249+
if (!hmac_name) {
250+
pr_warn("Invalid key hash id %d\n", key->hash);
251+
return ERR_PTR(-EINVAL);
252+
}
253+
254+
key_tfm = crypto_alloc_shash(hmac_name, 0, 0);
255+
if (IS_ERR(key_tfm))
256+
return (u8 *)key_tfm;
257+
258+
shash = kmalloc(sizeof(struct shash_desc) +
259+
crypto_shash_descsize(key_tfm),
260+
GFP_KERNEL);
261+
if (!shash) {
262+
ret = -ENOMEM;
263+
goto out_free_key;
264+
}
265+
266+
transformed_key = kzalloc(crypto_shash_digestsize(key_tfm), GFP_KERNEL);
267+
if (!transformed_key) {
268+
ret = -ENOMEM;
269+
goto out_free_shash;
270+
}
271+
272+
shash->tfm = key_tfm;
273+
ret = crypto_shash_setkey(key_tfm, key->key, key->len);
274+
if (ret < 0)
275+
goto out_free_shash;
276+
ret = crypto_shash_init(shash);
277+
if (ret < 0)
278+
goto out_free_shash;
279+
ret = crypto_shash_update(shash, nqn, strlen(nqn));
280+
if (ret < 0)
281+
goto out_free_shash;
282+
ret = crypto_shash_update(shash, "NVMe-over-Fabrics", 17);
283+
if (ret < 0)
284+
goto out_free_shash;
285+
ret = crypto_shash_final(shash, transformed_key);
286+
out_free_shash:
287+
kfree(shash);
288+
out_free_key:
289+
crypto_free_shash(key_tfm);
290+
if (ret < 0) {
291+
kfree_sensitive(transformed_key);
292+
return ERR_PTR(ret);
293+
}
294+
return transformed_key;
295+
}
296+
EXPORT_SYMBOL_GPL(nvme_auth_transform_key);
297+
298+
int nvme_auth_generate_key(u8 *secret, struct nvme_dhchap_key **ret_key)
299+
{
300+
struct nvme_dhchap_key *key;
301+
u8 key_hash;
302+
303+
if (!secret) {
304+
*ret_key = NULL;
305+
return 0;
306+
}
307+
308+
if (sscanf(secret, "DHHC-1:%hhd:%*s:", &key_hash) != 1)
309+
return -EINVAL;
310+
311+
/* Pass in the secret without the 'DHHC-1:XX:' prefix */
312+
key = nvme_auth_extract_key(secret + 10, key_hash);
313+
if (IS_ERR(key)) {
314+
*ret_key = NULL;
315+
return PTR_ERR(key);
316+
}
317+
318+
*ret_key = key;
319+
return 0;
320+
}
321+
EXPORT_SYMBOL_GPL(nvme_auth_generate_key);
322+
323+
MODULE_LICENSE("GPL v2");

drivers/nvme/host/Kconfig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ config NVME_TCP
9292

9393
If unsure, say N.
9494

95+
config NVME_AUTH
96+
bool "NVM Express over Fabrics In-Band Authentication"
97+
depends on NVME_CORE
98+
select NVME_COMMON
99+
select CRYPTO
100+
select CRYPTO_HMAC
101+
select CRYPTO_SHA256
102+
select CRYPTO_SHA512
103+
help
104+
This provides support for NVMe over Fabrics In-Band Authentication.
105+
106+
If unsure, say N.
107+
95108
config NVME_APPLE
96109
tristate "Apple ANS2 NVM Express host driver"
97110
depends on OF && BLOCK

drivers/nvme/host/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ nvme-core-$(CONFIG_NVME_MULTIPATH) += multipath.o
1616
nvme-core-$(CONFIG_BLK_DEV_ZONED) += zns.o
1717
nvme-core-$(CONFIG_FAULT_INJECTION_DEBUG_FS) += fault_inject.o
1818
nvme-core-$(CONFIG_NVME_HWMON) += hwmon.o
19+
nvme-core-$(CONFIG_NVME_AUTH) += auth.o
1920

2021
nvme-y += pci.o
2122

0 commit comments

Comments
 (0)