/src/cryptsetup/lib/luks2/luks2_digest_pbkdf2.c
Line | Count | Source (jump to first uncovered line) |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * LUKS - Linux Unified Key Setup v2, PBKDF2 digest handler (LUKS1 compatible) |
4 | | * |
5 | | * Copyright (C) 2015-2025 Red Hat, Inc. All rights reserved. |
6 | | * Copyright (C) 2015-2025 Milan Broz |
7 | | */ |
8 | | |
9 | | #include "luks2_internal.h" |
10 | | |
11 | 0 | #define LUKS_DIGESTSIZE 20 // since SHA1 |
12 | 0 | #define LUKS_SALTSIZE 32 |
13 | 0 | #define LUKS_MKD_ITERATIONS_MS 125 |
14 | | |
15 | | static int PBKDF2_digest_verify(struct crypt_device *cd, |
16 | | int digest, |
17 | | const char *volume_key, |
18 | | size_t volume_key_len) |
19 | 0 | { |
20 | 0 | char checkHashBuf[64]; |
21 | 0 | json_object *jobj_digest, *jobj1; |
22 | 0 | const char *hashSpec; |
23 | 0 | char *mkDigest = NULL, *mkDigestSalt = NULL; |
24 | 0 | unsigned int mkDigestIterations; |
25 | 0 | size_t len; |
26 | 0 | int r = -EINVAL; |
27 | | |
28 | | /* This can be done only for internally linked digests */ |
29 | 0 | jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest); |
30 | 0 | if (!jobj_digest) |
31 | 0 | return -EINVAL; |
32 | | |
33 | 0 | if (!json_object_object_get_ex(jobj_digest, "hash", &jobj1)) |
34 | 0 | return -EINVAL; |
35 | 0 | hashSpec = json_object_get_string(jobj1); |
36 | |
|
37 | 0 | if (!json_object_object_get_ex(jobj_digest, "iterations", &jobj1)) |
38 | 0 | return -EINVAL; |
39 | 0 | mkDigestIterations = json_object_get_int64(jobj1); |
40 | |
|
41 | 0 | if (!json_object_object_get_ex(jobj_digest, "salt", &jobj1)) |
42 | 0 | return -EINVAL; |
43 | 0 | r = crypt_base64_decode(&mkDigestSalt, &len, json_object_get_string(jobj1), |
44 | 0 | json_object_get_string_len(jobj1)); |
45 | 0 | if (r < 0) |
46 | 0 | goto out; |
47 | 0 | if (len != LUKS_SALTSIZE) |
48 | 0 | goto out; |
49 | | |
50 | 0 | if (!json_object_object_get_ex(jobj_digest, "digest", &jobj1)) |
51 | 0 | goto out; |
52 | 0 | r = crypt_base64_decode(&mkDigest, &len, json_object_get_string(jobj1), |
53 | 0 | json_object_get_string_len(jobj1)); |
54 | 0 | if (r < 0) |
55 | 0 | goto out; |
56 | 0 | if (len < LUKS_DIGESTSIZE || |
57 | 0 | len > sizeof(checkHashBuf) || |
58 | 0 | (len != LUKS_DIGESTSIZE && len != (size_t)crypt_hash_size(hashSpec))) |
59 | 0 | goto out; |
60 | | |
61 | 0 | r = -EPERM; |
62 | 0 | if (crypt_pbkdf(CRYPT_KDF_PBKDF2, hashSpec, volume_key, volume_key_len, |
63 | 0 | mkDigestSalt, LUKS_SALTSIZE, |
64 | 0 | checkHashBuf, len, |
65 | 0 | mkDigestIterations, 0, 0) < 0) { |
66 | 0 | r = -EINVAL; |
67 | 0 | } else { |
68 | 0 | if (crypt_backend_memeq(checkHashBuf, mkDigest, len) == 0) |
69 | 0 | r = 0; |
70 | 0 | } |
71 | 0 | out: |
72 | 0 | free(mkDigest); |
73 | 0 | free(mkDigestSalt); |
74 | 0 | return r; |
75 | 0 | } |
76 | | |
77 | | static int PBKDF2_digest_store(struct crypt_device *cd, |
78 | | int digest, |
79 | | const char *volume_key, |
80 | | size_t volume_key_len) |
81 | 0 | { |
82 | 0 | json_object *jobj_digest, *jobj_digests; |
83 | 0 | char salt[LUKS_SALTSIZE], digest_raw[128]; |
84 | 0 | int hmac_size, r; |
85 | 0 | char *base64_str; |
86 | 0 | struct luks2_hdr *hdr; |
87 | 0 | struct crypt_pbkdf_limits pbkdf_limits; |
88 | 0 | const struct crypt_pbkdf_type *pbkdf_cd; |
89 | 0 | struct crypt_pbkdf_type pbkdf = { |
90 | 0 | .type = CRYPT_KDF_PBKDF2, |
91 | 0 | .time_ms = LUKS_MKD_ITERATIONS_MS, |
92 | 0 | }; |
93 | | |
94 | | /* Inherit hash from PBKDF setting */ |
95 | 0 | pbkdf_cd = crypt_get_pbkdf_type(cd); |
96 | 0 | if (pbkdf_cd) |
97 | 0 | pbkdf.hash = pbkdf_cd->hash; |
98 | 0 | if (!pbkdf.hash) |
99 | 0 | pbkdf.hash = DEFAULT_LUKS1_HASH; |
100 | |
|
101 | 0 | log_dbg(cd, "Setting PBKDF2 type key digest %d.", digest); |
102 | |
|
103 | 0 | r = crypt_random_get(cd, salt, LUKS_SALTSIZE, CRYPT_RND_SALT); |
104 | 0 | if (r < 0) |
105 | 0 | return r; |
106 | | |
107 | 0 | r = crypt_pbkdf_get_limits(CRYPT_KDF_PBKDF2, &pbkdf_limits); |
108 | 0 | if (r < 0) |
109 | 0 | return r; |
110 | | |
111 | 0 | if (crypt_get_pbkdf(cd)->flags & CRYPT_PBKDF_NO_BENCHMARK) |
112 | 0 | pbkdf.iterations = pbkdf_limits.min_iterations; |
113 | 0 | else { |
114 | 0 | r = crypt_benchmark_pbkdf_internal(cd, &pbkdf, volume_key_len); |
115 | 0 | if (r < 0) |
116 | 0 | return r; |
117 | 0 | } |
118 | | |
119 | 0 | hmac_size = crypt_hmac_size(pbkdf.hash); |
120 | 0 | if (hmac_size < 0 || hmac_size > (int)sizeof(digest_raw)) |
121 | 0 | return -EINVAL; |
122 | | |
123 | 0 | r = crypt_pbkdf(CRYPT_KDF_PBKDF2, pbkdf.hash, volume_key, volume_key_len, |
124 | 0 | salt, LUKS_SALTSIZE, digest_raw, hmac_size, |
125 | 0 | pbkdf.iterations, 0, 0); |
126 | 0 | if (r < 0) |
127 | 0 | return r; |
128 | | |
129 | 0 | jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest); |
130 | 0 | jobj_digests = NULL; |
131 | 0 | if (!jobj_digest) { |
132 | 0 | hdr = crypt_get_hdr(cd, CRYPT_LUKS2); |
133 | 0 | jobj_digest = json_object_new_object(); |
134 | 0 | json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); |
135 | 0 | } |
136 | |
|
137 | 0 | if (!jobj_digest) |
138 | 0 | return -ENOMEM; |
139 | | |
140 | 0 | json_object_object_add(jobj_digest, "type", json_object_new_string("pbkdf2")); |
141 | 0 | json_object_object_add(jobj_digest, "keyslots", json_object_new_array()); |
142 | 0 | json_object_object_add(jobj_digest, "segments", json_object_new_array()); |
143 | 0 | json_object_object_add(jobj_digest, "hash", json_object_new_string(pbkdf.hash)); |
144 | 0 | json_object_object_add(jobj_digest, "iterations", json_object_new_int(pbkdf.iterations)); |
145 | |
|
146 | 0 | r = crypt_base64_encode(&base64_str, NULL, salt, LUKS_SALTSIZE); |
147 | 0 | if (r < 0) { |
148 | 0 | json_object_put(jobj_digest); |
149 | 0 | return r; |
150 | 0 | } |
151 | 0 | json_object_object_add(jobj_digest, "salt", json_object_new_string(base64_str)); |
152 | 0 | free(base64_str); |
153 | |
|
154 | 0 | r = crypt_base64_encode(&base64_str, NULL, digest_raw, hmac_size); |
155 | 0 | if (r < 0) { |
156 | 0 | json_object_put(jobj_digest); |
157 | 0 | return r; |
158 | 0 | } |
159 | 0 | json_object_object_add(jobj_digest, "digest", json_object_new_string(base64_str)); |
160 | 0 | free(base64_str); |
161 | |
|
162 | 0 | if (jobj_digests) { |
163 | 0 | r = json_object_object_add_by_uint(jobj_digests, digest, jobj_digest); |
164 | 0 | if (r < 0) { |
165 | 0 | json_object_put(jobj_digest); |
166 | 0 | return r; |
167 | 0 | } |
168 | 0 | } |
169 | | |
170 | 0 | JSON_DBG(cd, jobj_digest, "Digest JSON:"); |
171 | 0 | return 0; |
172 | 0 | } |
173 | | |
174 | | static int PBKDF2_digest_dump(struct crypt_device *cd, int digest) |
175 | 0 | { |
176 | 0 | json_object *jobj_digest, *jobj1; |
177 | | |
178 | | /* This can be done only for internally linked digests */ |
179 | 0 | jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest); |
180 | 0 | if (!jobj_digest) |
181 | 0 | return -EINVAL; |
182 | | |
183 | 0 | json_object_object_get_ex(jobj_digest, "hash", &jobj1); |
184 | 0 | log_std(cd, "\tHash: %s\n", json_object_get_string(jobj1)); |
185 | |
|
186 | 0 | json_object_object_get_ex(jobj_digest, "iterations", &jobj1); |
187 | 0 | log_std(cd, "\tIterations: %" PRIu64 "\n", json_object_get_int64(jobj1)); |
188 | |
|
189 | 0 | json_object_object_get_ex(jobj_digest, "salt", &jobj1); |
190 | 0 | log_std(cd, "\tSalt: "); |
191 | 0 | hexprint_base64(cd, jobj1, " ", " "); |
192 | |
|
193 | 0 | json_object_object_get_ex(jobj_digest, "digest", &jobj1); |
194 | 0 | log_std(cd, "\tDigest: "); |
195 | 0 | hexprint_base64(cd, jobj1, " ", " "); |
196 | |
|
197 | 0 | return 0; |
198 | 0 | } |
199 | | |
200 | | const digest_handler PBKDF2_digest = { |
201 | | .name = "pbkdf2", |
202 | | .verify = PBKDF2_digest_verify, |
203 | | .store = PBKDF2_digest_store, |
204 | | .dump = PBKDF2_digest_dump, |
205 | | }; |