1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * NVMe over Fabrics DH-HMAC-CHAP authentication. |
4 | * Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions. |
5 | * All rights reserved. |
6 | */ |
7 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
8 | #include <linux/module.h> |
9 | #include <linux/init.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/err.h> |
12 | #include <crypto/hash.h> |
13 | #include <linux/crc32.h> |
14 | #include <linux/base64.h> |
15 | #include <linux/ctype.h> |
16 | #include <linux/random.h> |
17 | #include <linux/nvme-auth.h> |
18 | #include <asm/unaligned.h> |
19 | |
20 | #include "nvmet.h" |
21 | |
22 | int nvmet_auth_set_key(struct nvmet_host *host, const char *secret, |
23 | bool set_ctrl) |
24 | { |
25 | unsigned char key_hash; |
26 | char *dhchap_secret; |
27 | |
28 | if (sscanf(secret, "DHHC-1:%hhd:%*s" , &key_hash) != 1) |
29 | return -EINVAL; |
30 | if (key_hash > 3) { |
31 | pr_warn("Invalid DH-HMAC-CHAP hash id %d\n" , |
32 | key_hash); |
33 | return -EINVAL; |
34 | } |
35 | if (key_hash > 0) { |
36 | /* Validate selected hash algorithm */ |
37 | const char *hmac = nvme_auth_hmac_name(hmac_id: key_hash); |
38 | |
39 | if (!crypto_has_shash(alg_name: hmac, type: 0, mask: 0)) { |
40 | pr_err("DH-HMAC-CHAP hash %s unsupported\n" , hmac); |
41 | return -ENOTSUPP; |
42 | } |
43 | } |
44 | dhchap_secret = kstrdup(s: secret, GFP_KERNEL); |
45 | if (!dhchap_secret) |
46 | return -ENOMEM; |
47 | if (set_ctrl) { |
48 | kfree(objp: host->dhchap_ctrl_secret); |
49 | host->dhchap_ctrl_secret = strim(dhchap_secret); |
50 | host->dhchap_ctrl_key_hash = key_hash; |
51 | } else { |
52 | kfree(objp: host->dhchap_secret); |
53 | host->dhchap_secret = strim(dhchap_secret); |
54 | host->dhchap_key_hash = key_hash; |
55 | } |
56 | return 0; |
57 | } |
58 | |
59 | int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, u8 dhgroup_id) |
60 | { |
61 | const char *dhgroup_kpp; |
62 | int ret = 0; |
63 | |
64 | pr_debug("%s: ctrl %d selecting dhgroup %d\n" , |
65 | __func__, ctrl->cntlid, dhgroup_id); |
66 | |
67 | if (ctrl->dh_tfm) { |
68 | if (ctrl->dh_gid == dhgroup_id) { |
69 | pr_debug("%s: ctrl %d reuse existing DH group %d\n" , |
70 | __func__, ctrl->cntlid, dhgroup_id); |
71 | return 0; |
72 | } |
73 | crypto_free_kpp(tfm: ctrl->dh_tfm); |
74 | ctrl->dh_tfm = NULL; |
75 | ctrl->dh_gid = 0; |
76 | } |
77 | |
78 | if (dhgroup_id == NVME_AUTH_DHGROUP_NULL) |
79 | return 0; |
80 | |
81 | dhgroup_kpp = nvme_auth_dhgroup_kpp(dhgroup_id); |
82 | if (!dhgroup_kpp) { |
83 | pr_debug("%s: ctrl %d invalid DH group %d\n" , |
84 | __func__, ctrl->cntlid, dhgroup_id); |
85 | return -EINVAL; |
86 | } |
87 | ctrl->dh_tfm = crypto_alloc_kpp(alg_name: dhgroup_kpp, type: 0, mask: 0); |
88 | if (IS_ERR(ptr: ctrl->dh_tfm)) { |
89 | pr_debug("%s: ctrl %d failed to setup DH group %d, err %ld\n" , |
90 | __func__, ctrl->cntlid, dhgroup_id, |
91 | PTR_ERR(ctrl->dh_tfm)); |
92 | ret = PTR_ERR(ptr: ctrl->dh_tfm); |
93 | ctrl->dh_tfm = NULL; |
94 | ctrl->dh_gid = 0; |
95 | } else { |
96 | ctrl->dh_gid = dhgroup_id; |
97 | pr_debug("%s: ctrl %d setup DH group %d\n" , |
98 | __func__, ctrl->cntlid, ctrl->dh_gid); |
99 | ret = nvme_auth_gen_privkey(dh_tfm: ctrl->dh_tfm, dh_gid: ctrl->dh_gid); |
100 | if (ret < 0) { |
101 | pr_debug("%s: ctrl %d failed to generate private key, err %d\n" , |
102 | __func__, ctrl->cntlid, ret); |
103 | kfree_sensitive(objp: ctrl->dh_key); |
104 | return ret; |
105 | } |
106 | ctrl->dh_keysize = crypto_kpp_maxsize(tfm: ctrl->dh_tfm); |
107 | kfree_sensitive(objp: ctrl->dh_key); |
108 | ctrl->dh_key = kzalloc(size: ctrl->dh_keysize, GFP_KERNEL); |
109 | if (!ctrl->dh_key) { |
110 | pr_warn("ctrl %d failed to allocate public key\n" , |
111 | ctrl->cntlid); |
112 | return -ENOMEM; |
113 | } |
114 | ret = nvme_auth_gen_pubkey(dh_tfm: ctrl->dh_tfm, host_key: ctrl->dh_key, |
115 | host_key_len: ctrl->dh_keysize); |
116 | if (ret < 0) { |
117 | pr_warn("ctrl %d failed to generate public key\n" , |
118 | ctrl->cntlid); |
119 | kfree(objp: ctrl->dh_key); |
120 | ctrl->dh_key = NULL; |
121 | } |
122 | } |
123 | |
124 | return ret; |
125 | } |
126 | |
127 | int nvmet_setup_auth(struct nvmet_ctrl *ctrl) |
128 | { |
129 | int ret = 0; |
130 | struct nvmet_host_link *p; |
131 | struct nvmet_host *host = NULL; |
132 | const char *hash_name; |
133 | |
134 | down_read(sem: &nvmet_config_sem); |
135 | if (nvmet_is_disc_subsys(subsys: ctrl->subsys)) |
136 | goto out_unlock; |
137 | |
138 | if (ctrl->subsys->allow_any_host) |
139 | goto out_unlock; |
140 | |
141 | list_for_each_entry(p, &ctrl->subsys->hosts, entry) { |
142 | pr_debug("check %s\n" , nvmet_host_name(p->host)); |
143 | if (strcmp(nvmet_host_name(host: p->host), ctrl->hostnqn)) |
144 | continue; |
145 | host = p->host; |
146 | break; |
147 | } |
148 | if (!host) { |
149 | pr_debug("host %s not found\n" , ctrl->hostnqn); |
150 | ret = -EPERM; |
151 | goto out_unlock; |
152 | } |
153 | |
154 | ret = nvmet_setup_dhgroup(ctrl, dhgroup_id: host->dhchap_dhgroup_id); |
155 | if (ret < 0) |
156 | pr_warn("Failed to setup DH group" ); |
157 | |
158 | if (!host->dhchap_secret) { |
159 | pr_debug("No authentication provided\n" ); |
160 | goto out_unlock; |
161 | } |
162 | |
163 | if (host->dhchap_hash_id == ctrl->shash_id) { |
164 | pr_debug("Re-use existing hash ID %d\n" , |
165 | ctrl->shash_id); |
166 | } else { |
167 | hash_name = nvme_auth_hmac_name(hmac_id: host->dhchap_hash_id); |
168 | if (!hash_name) { |
169 | pr_warn("Hash ID %d invalid\n" , host->dhchap_hash_id); |
170 | ret = -EINVAL; |
171 | goto out_unlock; |
172 | } |
173 | ctrl->shash_id = host->dhchap_hash_id; |
174 | } |
175 | |
176 | /* Skip the 'DHHC-1:XX:' prefix */ |
177 | nvme_auth_free_key(key: ctrl->host_key); |
178 | ctrl->host_key = nvme_auth_extract_key(secret: host->dhchap_secret + 10, |
179 | key_hash: host->dhchap_key_hash); |
180 | if (IS_ERR(ptr: ctrl->host_key)) { |
181 | ret = PTR_ERR(ptr: ctrl->host_key); |
182 | ctrl->host_key = NULL; |
183 | goto out_free_hash; |
184 | } |
185 | pr_debug("%s: using hash %s key %*ph\n" , __func__, |
186 | ctrl->host_key->hash > 0 ? |
187 | nvme_auth_hmac_name(ctrl->host_key->hash) : "none" , |
188 | (int)ctrl->host_key->len, ctrl->host_key->key); |
189 | |
190 | nvme_auth_free_key(key: ctrl->ctrl_key); |
191 | if (!host->dhchap_ctrl_secret) { |
192 | ctrl->ctrl_key = NULL; |
193 | goto out_unlock; |
194 | } |
195 | |
196 | ctrl->ctrl_key = nvme_auth_extract_key(secret: host->dhchap_ctrl_secret + 10, |
197 | key_hash: host->dhchap_ctrl_key_hash); |
198 | if (IS_ERR(ptr: ctrl->ctrl_key)) { |
199 | ret = PTR_ERR(ptr: ctrl->ctrl_key); |
200 | ctrl->ctrl_key = NULL; |
201 | goto out_free_hash; |
202 | } |
203 | pr_debug("%s: using ctrl hash %s key %*ph\n" , __func__, |
204 | ctrl->ctrl_key->hash > 0 ? |
205 | nvme_auth_hmac_name(ctrl->ctrl_key->hash) : "none" , |
206 | (int)ctrl->ctrl_key->len, ctrl->ctrl_key->key); |
207 | |
208 | out_free_hash: |
209 | if (ret) { |
210 | if (ctrl->host_key) { |
211 | nvme_auth_free_key(key: ctrl->host_key); |
212 | ctrl->host_key = NULL; |
213 | } |
214 | ctrl->shash_id = 0; |
215 | } |
216 | out_unlock: |
217 | up_read(sem: &nvmet_config_sem); |
218 | |
219 | return ret; |
220 | } |
221 | |
222 | void nvmet_auth_sq_free(struct nvmet_sq *sq) |
223 | { |
224 | cancel_delayed_work(dwork: &sq->auth_expired_work); |
225 | kfree(objp: sq->dhchap_c1); |
226 | sq->dhchap_c1 = NULL; |
227 | kfree(objp: sq->dhchap_c2); |
228 | sq->dhchap_c2 = NULL; |
229 | kfree(objp: sq->dhchap_skey); |
230 | sq->dhchap_skey = NULL; |
231 | } |
232 | |
233 | void nvmet_destroy_auth(struct nvmet_ctrl *ctrl) |
234 | { |
235 | ctrl->shash_id = 0; |
236 | |
237 | if (ctrl->dh_tfm) { |
238 | crypto_free_kpp(tfm: ctrl->dh_tfm); |
239 | ctrl->dh_tfm = NULL; |
240 | ctrl->dh_gid = 0; |
241 | } |
242 | kfree_sensitive(objp: ctrl->dh_key); |
243 | ctrl->dh_key = NULL; |
244 | |
245 | if (ctrl->host_key) { |
246 | nvme_auth_free_key(key: ctrl->host_key); |
247 | ctrl->host_key = NULL; |
248 | } |
249 | if (ctrl->ctrl_key) { |
250 | nvme_auth_free_key(key: ctrl->ctrl_key); |
251 | ctrl->ctrl_key = NULL; |
252 | } |
253 | } |
254 | |
255 | bool nvmet_check_auth_status(struct nvmet_req *req) |
256 | { |
257 | if (req->sq->ctrl->host_key && |
258 | !req->sq->authenticated) |
259 | return false; |
260 | return true; |
261 | } |
262 | |
263 | int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response, |
264 | unsigned int shash_len) |
265 | { |
266 | struct crypto_shash *shash_tfm; |
267 | struct shash_desc *shash; |
268 | struct nvmet_ctrl *ctrl = req->sq->ctrl; |
269 | const char *hash_name; |
270 | u8 *challenge = req->sq->dhchap_c1; |
271 | struct nvme_dhchap_key *transformed_key; |
272 | u8 buf[4]; |
273 | int ret; |
274 | |
275 | hash_name = nvme_auth_hmac_name(hmac_id: ctrl->shash_id); |
276 | if (!hash_name) { |
277 | pr_warn("Hash ID %d invalid\n" , ctrl->shash_id); |
278 | return -EINVAL; |
279 | } |
280 | |
281 | shash_tfm = crypto_alloc_shash(alg_name: hash_name, type: 0, mask: 0); |
282 | if (IS_ERR(ptr: shash_tfm)) { |
283 | pr_err("failed to allocate shash %s\n" , hash_name); |
284 | return PTR_ERR(ptr: shash_tfm); |
285 | } |
286 | |
287 | if (shash_len != crypto_shash_digestsize(tfm: shash_tfm)) { |
288 | pr_debug("%s: hash len mismatch (len %d digest %d)\n" , |
289 | __func__, shash_len, |
290 | crypto_shash_digestsize(shash_tfm)); |
291 | ret = -EINVAL; |
292 | goto out_free_tfm; |
293 | } |
294 | |
295 | transformed_key = nvme_auth_transform_key(key: ctrl->host_key, |
296 | nqn: ctrl->hostnqn); |
297 | if (IS_ERR(ptr: transformed_key)) { |
298 | ret = PTR_ERR(ptr: transformed_key); |
299 | goto out_free_tfm; |
300 | } |
301 | |
302 | ret = crypto_shash_setkey(tfm: shash_tfm, key: transformed_key->key, |
303 | keylen: transformed_key->len); |
304 | if (ret) |
305 | goto out_free_response; |
306 | |
307 | if (ctrl->dh_gid != NVME_AUTH_DHGROUP_NULL) { |
308 | challenge = kmalloc(size: shash_len, GFP_KERNEL); |
309 | if (!challenge) { |
310 | ret = -ENOMEM; |
311 | goto out_free_response; |
312 | } |
313 | ret = nvme_auth_augmented_challenge(hmac_id: ctrl->shash_id, |
314 | skey: req->sq->dhchap_skey, |
315 | skey_len: req->sq->dhchap_skey_len, |
316 | challenge: req->sq->dhchap_c1, |
317 | aug: challenge, hlen: shash_len); |
318 | if (ret) |
319 | goto out_free_response; |
320 | } |
321 | |
322 | pr_debug("ctrl %d qid %d host response seq %u transaction %d\n" , |
323 | ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1, |
324 | req->sq->dhchap_tid); |
325 | |
326 | shash = kzalloc(size: sizeof(*shash) + crypto_shash_descsize(tfm: shash_tfm), |
327 | GFP_KERNEL); |
328 | if (!shash) { |
329 | ret = -ENOMEM; |
330 | goto out_free_response; |
331 | } |
332 | shash->tfm = shash_tfm; |
333 | ret = crypto_shash_init(desc: shash); |
334 | if (ret) |
335 | goto out; |
336 | ret = crypto_shash_update(desc: shash, data: challenge, len: shash_len); |
337 | if (ret) |
338 | goto out; |
339 | put_unaligned_le32(val: req->sq->dhchap_s1, p: buf); |
340 | ret = crypto_shash_update(desc: shash, data: buf, len: 4); |
341 | if (ret) |
342 | goto out; |
343 | put_unaligned_le16(val: req->sq->dhchap_tid, p: buf); |
344 | ret = crypto_shash_update(desc: shash, data: buf, len: 2); |
345 | if (ret) |
346 | goto out; |
347 | memset(buf, 0, 4); |
348 | ret = crypto_shash_update(desc: shash, data: buf, len: 1); |
349 | if (ret) |
350 | goto out; |
351 | ret = crypto_shash_update(desc: shash, data: "HostHost" , len: 8); |
352 | if (ret) |
353 | goto out; |
354 | ret = crypto_shash_update(desc: shash, data: ctrl->hostnqn, strlen(ctrl->hostnqn)); |
355 | if (ret) |
356 | goto out; |
357 | ret = crypto_shash_update(desc: shash, data: buf, len: 1); |
358 | if (ret) |
359 | goto out; |
360 | ret = crypto_shash_update(desc: shash, data: ctrl->subsysnqn, |
361 | strlen(ctrl->subsysnqn)); |
362 | if (ret) |
363 | goto out; |
364 | ret = crypto_shash_final(desc: shash, out: response); |
365 | out: |
366 | if (challenge != req->sq->dhchap_c1) |
367 | kfree(objp: challenge); |
368 | kfree(objp: shash); |
369 | out_free_response: |
370 | nvme_auth_free_key(key: transformed_key); |
371 | out_free_tfm: |
372 | crypto_free_shash(tfm: shash_tfm); |
373 | return 0; |
374 | } |
375 | |
376 | int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response, |
377 | unsigned int shash_len) |
378 | { |
379 | struct crypto_shash *shash_tfm; |
380 | struct shash_desc *shash; |
381 | struct nvmet_ctrl *ctrl = req->sq->ctrl; |
382 | const char *hash_name; |
383 | u8 *challenge = req->sq->dhchap_c2; |
384 | struct nvme_dhchap_key *transformed_key; |
385 | u8 buf[4]; |
386 | int ret; |
387 | |
388 | hash_name = nvme_auth_hmac_name(hmac_id: ctrl->shash_id); |
389 | if (!hash_name) { |
390 | pr_warn("Hash ID %d invalid\n" , ctrl->shash_id); |
391 | return -EINVAL; |
392 | } |
393 | |
394 | shash_tfm = crypto_alloc_shash(alg_name: hash_name, type: 0, mask: 0); |
395 | if (IS_ERR(ptr: shash_tfm)) { |
396 | pr_err("failed to allocate shash %s\n" , hash_name); |
397 | return PTR_ERR(ptr: shash_tfm); |
398 | } |
399 | |
400 | if (shash_len != crypto_shash_digestsize(tfm: shash_tfm)) { |
401 | pr_debug("%s: hash len mismatch (len %d digest %d)\n" , |
402 | __func__, shash_len, |
403 | crypto_shash_digestsize(shash_tfm)); |
404 | ret = -EINVAL; |
405 | goto out_free_tfm; |
406 | } |
407 | |
408 | transformed_key = nvme_auth_transform_key(key: ctrl->ctrl_key, |
409 | nqn: ctrl->subsysnqn); |
410 | if (IS_ERR(ptr: transformed_key)) { |
411 | ret = PTR_ERR(ptr: transformed_key); |
412 | goto out_free_tfm; |
413 | } |
414 | |
415 | ret = crypto_shash_setkey(tfm: shash_tfm, key: transformed_key->key, |
416 | keylen: transformed_key->len); |
417 | if (ret) |
418 | goto out_free_response; |
419 | |
420 | if (ctrl->dh_gid != NVME_AUTH_DHGROUP_NULL) { |
421 | challenge = kmalloc(size: shash_len, GFP_KERNEL); |
422 | if (!challenge) { |
423 | ret = -ENOMEM; |
424 | goto out_free_response; |
425 | } |
426 | ret = nvme_auth_augmented_challenge(hmac_id: ctrl->shash_id, |
427 | skey: req->sq->dhchap_skey, |
428 | skey_len: req->sq->dhchap_skey_len, |
429 | challenge: req->sq->dhchap_c2, |
430 | aug: challenge, hlen: shash_len); |
431 | if (ret) |
432 | goto out_free_response; |
433 | } |
434 | |
435 | shash = kzalloc(size: sizeof(*shash) + crypto_shash_descsize(tfm: shash_tfm), |
436 | GFP_KERNEL); |
437 | if (!shash) { |
438 | ret = -ENOMEM; |
439 | goto out_free_response; |
440 | } |
441 | shash->tfm = shash_tfm; |
442 | |
443 | ret = crypto_shash_init(desc: shash); |
444 | if (ret) |
445 | goto out; |
446 | ret = crypto_shash_update(desc: shash, data: challenge, len: shash_len); |
447 | if (ret) |
448 | goto out; |
449 | put_unaligned_le32(val: req->sq->dhchap_s2, p: buf); |
450 | ret = crypto_shash_update(desc: shash, data: buf, len: 4); |
451 | if (ret) |
452 | goto out; |
453 | put_unaligned_le16(val: req->sq->dhchap_tid, p: buf); |
454 | ret = crypto_shash_update(desc: shash, data: buf, len: 2); |
455 | if (ret) |
456 | goto out; |
457 | memset(buf, 0, 4); |
458 | ret = crypto_shash_update(desc: shash, data: buf, len: 1); |
459 | if (ret) |
460 | goto out; |
461 | ret = crypto_shash_update(desc: shash, data: "Controller" , len: 10); |
462 | if (ret) |
463 | goto out; |
464 | ret = crypto_shash_update(desc: shash, data: ctrl->subsysnqn, |
465 | strlen(ctrl->subsysnqn)); |
466 | if (ret) |
467 | goto out; |
468 | ret = crypto_shash_update(desc: shash, data: buf, len: 1); |
469 | if (ret) |
470 | goto out; |
471 | ret = crypto_shash_update(desc: shash, data: ctrl->hostnqn, strlen(ctrl->hostnqn)); |
472 | if (ret) |
473 | goto out; |
474 | ret = crypto_shash_final(desc: shash, out: response); |
475 | out: |
476 | if (challenge != req->sq->dhchap_c2) |
477 | kfree(objp: challenge); |
478 | kfree(objp: shash); |
479 | out_free_response: |
480 | nvme_auth_free_key(key: transformed_key); |
481 | out_free_tfm: |
482 | crypto_free_shash(tfm: shash_tfm); |
483 | return 0; |
484 | } |
485 | |
486 | int nvmet_auth_ctrl_exponential(struct nvmet_req *req, |
487 | u8 *buf, int buf_size) |
488 | { |
489 | struct nvmet_ctrl *ctrl = req->sq->ctrl; |
490 | int ret = 0; |
491 | |
492 | if (!ctrl->dh_key) { |
493 | pr_warn("ctrl %d no DH public key!\n" , ctrl->cntlid); |
494 | return -ENOKEY; |
495 | } |
496 | if (buf_size != ctrl->dh_keysize) { |
497 | pr_warn("ctrl %d DH public key size mismatch, need %zu is %d\n" , |
498 | ctrl->cntlid, ctrl->dh_keysize, buf_size); |
499 | ret = -EINVAL; |
500 | } else { |
501 | memcpy(buf, ctrl->dh_key, buf_size); |
502 | pr_debug("%s: ctrl %d public key %*ph\n" , __func__, |
503 | ctrl->cntlid, (int)buf_size, buf); |
504 | } |
505 | |
506 | return ret; |
507 | } |
508 | |
509 | int nvmet_auth_ctrl_sesskey(struct nvmet_req *req, |
510 | u8 *pkey, int pkey_size) |
511 | { |
512 | struct nvmet_ctrl *ctrl = req->sq->ctrl; |
513 | int ret; |
514 | |
515 | req->sq->dhchap_skey_len = ctrl->dh_keysize; |
516 | req->sq->dhchap_skey = kzalloc(size: req->sq->dhchap_skey_len, GFP_KERNEL); |
517 | if (!req->sq->dhchap_skey) |
518 | return -ENOMEM; |
519 | ret = nvme_auth_gen_shared_secret(dh_tfm: ctrl->dh_tfm, |
520 | ctrl_key: pkey, ctrl_key_len: pkey_size, |
521 | sess_key: req->sq->dhchap_skey, |
522 | sess_key_len: req->sq->dhchap_skey_len); |
523 | if (ret) |
524 | pr_debug("failed to compute shared secret, err %d\n" , ret); |
525 | else |
526 | pr_debug("%s: shared secret %*ph\n" , __func__, |
527 | (int)req->sq->dhchap_skey_len, |
528 | req->sq->dhchap_skey); |
529 | |
530 | return ret; |
531 | } |
532 | |