summaryrefslogtreecommitdiffstats
path: root/lib/luks2/luks2_digest_pbkdf2.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/luks2/luks2_digest_pbkdf2.c')
-rw-r--r--lib/luks2/luks2_digest_pbkdf2.c210
1 files changed, 210 insertions, 0 deletions
diff --git a/lib/luks2/luks2_digest_pbkdf2.c b/lib/luks2/luks2_digest_pbkdf2.c
new file mode 100644
index 0000000..1009cfb
--- /dev/null
+++ b/lib/luks2/luks2_digest_pbkdf2.c
@@ -0,0 +1,210 @@
+/*
+ * LUKS - Linux Unified Key Setup v2, PBKDF2 digest handler (LUKS1 compatible)
+ *
+ * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2015-2023 Milan Broz
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "luks2_internal.h"
+
+#define LUKS_DIGESTSIZE 20 // since SHA1
+#define LUKS_SALTSIZE 32
+#define LUKS_MKD_ITERATIONS_MS 125
+
+static int PBKDF2_digest_verify(struct crypt_device *cd,
+ int digest,
+ const char *volume_key,
+ size_t volume_key_len)
+{
+ char checkHashBuf[64];
+ json_object *jobj_digest, *jobj1;
+ const char *hashSpec;
+ char *mkDigest = NULL, *mkDigestSalt = NULL;
+ unsigned int mkDigestIterations;
+ size_t len;
+ int r = -EINVAL;
+
+ /* This can be done only for internally linked digests */
+ jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest);
+ if (!jobj_digest)
+ return -EINVAL;
+
+ if (!json_object_object_get_ex(jobj_digest, "hash", &jobj1))
+ return -EINVAL;
+ hashSpec = json_object_get_string(jobj1);
+
+ if (!json_object_object_get_ex(jobj_digest, "iterations", &jobj1))
+ return -EINVAL;
+ mkDigestIterations = json_object_get_int64(jobj1);
+
+ if (!json_object_object_get_ex(jobj_digest, "salt", &jobj1))
+ return -EINVAL;
+ r = crypt_base64_decode(&mkDigestSalt, &len, json_object_get_string(jobj1),
+ json_object_get_string_len(jobj1));
+ if (r < 0)
+ goto out;
+ if (len != LUKS_SALTSIZE)
+ goto out;
+
+ if (!json_object_object_get_ex(jobj_digest, "digest", &jobj1))
+ goto out;
+ r = crypt_base64_decode(&mkDigest, &len, json_object_get_string(jobj1),
+ json_object_get_string_len(jobj1));
+ if (r < 0)
+ goto out;
+ if (len < LUKS_DIGESTSIZE ||
+ len > sizeof(checkHashBuf) ||
+ (len != LUKS_DIGESTSIZE && len != (size_t)crypt_hash_size(hashSpec)))
+ goto out;
+
+ r = -EPERM;
+ if (crypt_pbkdf(CRYPT_KDF_PBKDF2, hashSpec, volume_key, volume_key_len,
+ mkDigestSalt, LUKS_SALTSIZE,
+ checkHashBuf, len,
+ mkDigestIterations, 0, 0) < 0) {
+ r = -EINVAL;
+ } else {
+ if (crypt_backend_memeq(checkHashBuf, mkDigest, len) == 0)
+ r = 0;
+ }
+out:
+ free(mkDigest);
+ free(mkDigestSalt);
+ return r;
+}
+
+static int PBKDF2_digest_store(struct crypt_device *cd,
+ int digest,
+ const char *volume_key,
+ size_t volume_key_len)
+{
+ json_object *jobj_digest, *jobj_digests;
+ char salt[LUKS_SALTSIZE], digest_raw[128];
+ int hmac_size, r;
+ char *base64_str;
+ struct luks2_hdr *hdr;
+ struct crypt_pbkdf_limits pbkdf_limits;
+ const struct crypt_pbkdf_type *pbkdf_cd;
+ struct crypt_pbkdf_type pbkdf = {
+ .type = CRYPT_KDF_PBKDF2,
+ .time_ms = LUKS_MKD_ITERATIONS_MS,
+ };
+
+ /* Inherit hash from PBKDF setting */
+ pbkdf_cd = crypt_get_pbkdf_type(cd);
+ if (pbkdf_cd)
+ pbkdf.hash = pbkdf_cd->hash;
+ if (!pbkdf.hash)
+ pbkdf.hash = DEFAULT_LUKS1_HASH;
+
+ log_dbg(cd, "Setting PBKDF2 type key digest %d.", digest);
+
+ r = crypt_random_get(cd, salt, LUKS_SALTSIZE, CRYPT_RND_SALT);
+ if (r < 0)
+ return r;
+
+ r = crypt_pbkdf_get_limits(CRYPT_KDF_PBKDF2, &pbkdf_limits);
+ if (r < 0)
+ return r;
+
+ if (crypt_get_pbkdf(cd)->flags & CRYPT_PBKDF_NO_BENCHMARK)
+ pbkdf.iterations = pbkdf_limits.min_iterations;
+ else {
+ r = crypt_benchmark_pbkdf_internal(cd, &pbkdf, volume_key_len);
+ if (r < 0)
+ return r;
+ }
+
+ hmac_size = crypt_hmac_size(pbkdf.hash);
+ if (hmac_size < 0 || hmac_size > (int)sizeof(digest_raw))
+ return -EINVAL;
+
+ r = crypt_pbkdf(CRYPT_KDF_PBKDF2, pbkdf.hash, volume_key, volume_key_len,
+ salt, LUKS_SALTSIZE, digest_raw, hmac_size,
+ pbkdf.iterations, 0, 0);
+ if (r < 0)
+ return r;
+
+ jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest);
+ jobj_digests = NULL;
+ if (!jobj_digest) {
+ hdr = crypt_get_hdr(cd, CRYPT_LUKS2);
+ jobj_digest = json_object_new_object();
+ json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests);
+ }
+
+ json_object_object_add(jobj_digest, "type", json_object_new_string("pbkdf2"));
+ json_object_object_add(jobj_digest, "keyslots", json_object_new_array());
+ json_object_object_add(jobj_digest, "segments", json_object_new_array());
+ json_object_object_add(jobj_digest, "hash", json_object_new_string(pbkdf.hash));
+ json_object_object_add(jobj_digest, "iterations", json_object_new_int(pbkdf.iterations));
+
+ r = crypt_base64_encode(&base64_str, NULL, salt, LUKS_SALTSIZE);
+ if (r < 0) {
+ json_object_put(jobj_digest);
+ return r;
+ }
+ json_object_object_add(jobj_digest, "salt", json_object_new_string(base64_str));
+ free(base64_str);
+
+ r = crypt_base64_encode(&base64_str, NULL, digest_raw, hmac_size);
+ if (r < 0) {
+ json_object_put(jobj_digest);
+ return r;
+ }
+ json_object_object_add(jobj_digest, "digest", json_object_new_string(base64_str));
+ free(base64_str);
+
+ if (jobj_digests)
+ json_object_object_add_by_uint(jobj_digests, digest, jobj_digest);
+
+ JSON_DBG(cd, jobj_digest, "Digest JSON:");
+ return 0;
+}
+
+static int PBKDF2_digest_dump(struct crypt_device *cd, int digest)
+{
+ json_object *jobj_digest, *jobj1;
+
+ /* This can be done only for internally linked digests */
+ jobj_digest = LUKS2_get_digest_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), digest);
+ if (!jobj_digest)
+ return -EINVAL;
+
+ json_object_object_get_ex(jobj_digest, "hash", &jobj1);
+ log_std(cd, "\tHash: %s\n", json_object_get_string(jobj1));
+
+ json_object_object_get_ex(jobj_digest, "iterations", &jobj1);
+ log_std(cd, "\tIterations: %" PRIu64 "\n", json_object_get_int64(jobj1));
+
+ json_object_object_get_ex(jobj_digest, "salt", &jobj1);
+ log_std(cd, "\tSalt: ");
+ hexprint_base64(cd, jobj1, " ", " ");
+
+ json_object_object_get_ex(jobj_digest, "digest", &jobj1);
+ log_std(cd, "\tDigest: ");
+ hexprint_base64(cd, jobj1, " ", " ");
+
+ return 0;
+}
+
+const digest_handler PBKDF2_digest = {
+ .name = "pbkdf2",
+ .verify = PBKDF2_digest_verify,
+ .store = PBKDF2_digest_store,
+ .dump = PBKDF2_digest_dump,
+};