Skip to content

Commit

Permalink
Add ZFS_PROP_PASSPHRASE_KDF (passphrasekdf) property and argon2id
Browse files Browse the repository at this point in the history
Signed-off-by: Ahelenia Ziemiańska <[email protected]>
  • Loading branch information
nabijaczleweli committed Feb 8, 2024
1 parent 20c52ee commit 5153c92
Show file tree
Hide file tree
Showing 19 changed files with 374 additions and 106 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build-dependencies.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ libmount-dev
libpam0g-dev
libselinux1-dev
libssl-dev
libargon2-dev
libtool
libudev-dev
linux-headers-generic
Expand Down
36 changes: 31 additions & 5 deletions cmd/zdb/zdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <stdlib.h>
#include <ctype.h>
#include <getopt.h>
#include <argon2.h>
#include <openssl/evp.h>
#include <sys/zfs_context.h>
#include <sys/spa.h>
Expand Down Expand Up @@ -3123,7 +3124,7 @@ static char *key_material = NULL;
static boolean_t
zdb_derive_key(dsl_dir_t *dd, uint8_t *key_out)
{
uint64_t keyformat, salt, iters;
uint64_t keyformat, kdf = ZFS_PASSPHRASE_KDF_PBKDF2, salt, iters;
int i;
unsigned char c;

Expand Down Expand Up @@ -3151,10 +3152,35 @@ zdb_derive_key(dsl_dir_t *dd, uint8_t *key_out)
dd->dd_crypto_obj, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS),
sizeof (uint64_t), 1, &iters));

if (PKCS5_PBKDF2_HMAC_SHA1(key_material, strlen(key_material),
((uint8_t *)&salt), sizeof (uint64_t), iters,
WRAPPING_KEY_LEN, key_out) != 1)
return (B_FALSE);
int err = zap_lookup(dd->dd_pool->dp_meta_objset,
dd->dd_crypto_obj,
zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF),
sizeof (uint64_t), 1, &kdf);
VERIFY(err == 0 || err == ENOENT);

switch (kdf) {
case ZFS_PASSPHRASE_KDF_PBKDF2:
if (PKCS5_PBKDF2_HMAC_SHA1(key_material,
strlen(key_material), ((uint8_t *)&salt),
sizeof (uint64_t), iters,
WRAPPING_KEY_LEN, key_out) != 1)
return (B_FALSE);
break;
case ZFS_PASSPHRASE_KDF_ARGON2ID:
zfs_passphrase_kdf_argon2id_params_t params =
zfs_passphrase_kdf_argon2id_params(iters);
if (argon2_hash(params.t_cost, params.m_cost,
params.parallelism,
key_material, strlen(key_material),
&salt, sizeof (uint64_t), key_out, WRAPPING_KEY_LEN,
NULL, 0, Argon2_id, ARGON2_VERSION_13)
!= ARGON2_OK)
return (B_FALSE);
break;
default:
fatal("no support for KDF %u\n",
(unsigned int) kdf);
}

break;

Expand Down
5 changes: 3 additions & 2 deletions cmd/zfs/zfs_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,9 @@ get_usage(zfs_help_t idx)
"<-a | filesystem|volume>\n"));
case HELP_CHANGE_KEY:
return (gettext("\tchange-key [-l] [-o keyformat=<value>]\n"
"\t [-o keylocation=<value>] [-o pbkdf2iters=<value>]\n"
"\t <filesystem|volume>\n"
"\t [-o keylocation=<value>] "
"[-o passphrasekdf=<value>] \n"
"\t [-o pbkdf2iters=<value>] <filesystem|volume>\n"
"\tchange-key -i [-l] <filesystem|volume>\n"));
case HELP_VERSION:
return (gettext("\tversion\n"));
Expand Down
3 changes: 3 additions & 0 deletions cmd/ztest.c
Original file line number Diff line number Diff line change
Expand Up @@ -4553,6 +4553,9 @@ ztest_dataset_create(char *dsname)
zfs_prop_to_name(ZFS_PROP_KEYFORMAT), ZFS_KEYFORMAT_RAW);
fnvlist_add_string(props,
zfs_prop_to_name(ZFS_PROP_KEYLOCATION), "prompt");
fnvlist_add_uint64(props,
zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF),
ztest_random(ZFS_PASSPHRASE_KDF_KDFS));
fnvlist_add_uint64(props,
zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), 0ULL);
fnvlist_add_uint64(props,
Expand Down
16 changes: 12 additions & 4 deletions config/user-libcrypto.m4
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
dnl #
dnl # Check for libcrypto. Used for userspace password derivation via PBKDF2.
dnl # Check for libcrypto and libargon. Used for userspace password derivation via PBKDF2 and argon2id.
dnl #
AC_DEFUN([ZFS_AC_CONFIG_USER_LIBCRYPTO], [
ZFS_AC_FIND_SYSTEM_LIBRARY(LIBCRYPTO, [libcrypto], [openssl/evp.h], [], [crypto], [PKCS5_PBKDF2_HMAC_SHA1], [], [
AC_MSG_FAILURE([
*** evp.h missing, libssl-devel package required])])
ZFS_AC_FIND_SYSTEM_LIBRARY(LIBCRYPTO_SSL, [libcrypto], [openssl/evp.h], [], [crypto], [PKCS5_PBKDF2_HMAC_SHA1], [], [
AC_MSG_FAILURE([*** evp.h missing, libssl-devel package required])])
# ARGON2 is included in openssl 3.2: once this is widely distributed, we should detect it and drop the libargon2 dep
ZFS_AC_FIND_SYSTEM_LIBRARY(LIBCRYPTO_ARGON2, [libargon2], [argon2.h], [], [argon2], [argon2id_hash_raw], [], [
AC_MSG_FAILURE([*** libargon2-dev package required])])
LIBCRYPTO_CFLAGS="$LIBCRYPTO_SSL_CFLAGS $LIBCRYPTO_ARGON2_CFLAGS"
LIBCRYPTO_LIBS="$LIBCRYPTO_SSL_LIBS $LIBCRYPTO_ARGON2_LIBS"
AC_SUBST(LIBCRYPTO_CFLAGS, [])
AC_SUBST(LIBCRYPTO_LIBS, [])
])
73 changes: 50 additions & 23 deletions contrib/pam_zfs_key/pam_zfs_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include <sys/zio_crypt.h>
#include <openssl/evp.h>
#include <argon2.h>

#define PAM_SM_AUTH
#define PAM_SM_PASSWORD
Expand Down Expand Up @@ -250,17 +251,24 @@ static pw_password_t *
prepare_passphrase(pam_handle_t *pamh, zfs_handle_t *ds,
const char *passphrase, nvlist_t *nvlist)
{
const char *errstr = NULL;
pw_password_t *key = alloc_pw_size(WRAPPING_KEY_LEN);
if (!key) {
return (NULL);
}
uint64_t salt;
uint64_t iters;
uint64_t kdf, salt, iters;
if (nvlist != NULL) {
kdf = ZFS_PASSPHRASE_KDF_PBKDF2;
if (nvlist_add_uint64(nvlist,
zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), kdf)) {
errstr = "failed to add KDF to nvlist";
goto err;
}

int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0) {
pw_free(key);
return (NULL);
errstr = "/dev/urandom";
goto err;
}
int bytes_read = 0;
char *buf = (char *)&salt;
Expand All @@ -270,43 +278,62 @@ prepare_passphrase(pam_handle_t *pamh, zfs_handle_t *ds,
- bytes_read);
if (len < 0) {
close(fd);
pw_free(key);
return (NULL);
errstr = "failed to read salt";
goto err;
}
bytes_read += len;
}
close(fd);

if (nvlist_add_uint64(nvlist,
zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt)) {
pam_syslog(pamh, LOG_ERR,
"failed to add salt to nvlist");
pw_free(key);
return (NULL);
errstr = "failed to add salt to nvlist";
goto err;
}
iters = DEFAULT_PBKDF2_ITERATIONS;
iters = zfs_passphrase_kdf_default_parameters[kdf];
if (nvlist_add_uint64(nvlist, zfs_prop_to_name(
ZFS_PROP_PBKDF2_ITERS), iters)) {
pam_syslog(pamh, LOG_ERR,
"failed to add iters to nvlist");
pw_free(key);
return (NULL);
errstr = "failed to add iters to nvlist";
goto err;
}
} else {
kdf = zfs_prop_get_int(ds, ZFS_PROP_PASSPHRASE_KDF);
salt = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_SALT);
iters = zfs_prop_get_int(ds, ZFS_PROP_PBKDF2_ITERS);
}

salt = LE_64(salt);
if (!PKCS5_PBKDF2_HMAC_SHA1((char *)passphrase,
strlen(passphrase), (uint8_t *)&salt,
sizeof (uint64_t), iters, WRAPPING_KEY_LEN,
(uint8_t *)key->value)) {
pam_syslog(pamh, LOG_ERR, "pbkdf failed");
pw_free(key);
return (NULL);
}

switch (kdf) {
case ZFS_PASSPHRASE_KDF_PBKDF2:
if (PKCS5_PBKDF2_HMAC_SHA1((char *)passphrase,
strlen(passphrase), ((uint8_t *)&salt),
sizeof (uint64_t), iters, WRAPPING_KEY_LEN,
(uint8_t *)key->value) != 1)
errstr = "PBKDF2 failed";
break;
case ZFS_PASSPHRASE_KDF_ARGON2ID:
zfs_passphrase_kdf_argon2id_params_t params =
zfs_passphrase_kdf_argon2id_params(iters);
if (argon2_hash(params.t_cost, params.m_cost, params.parallelism,
passphrase, strlen((char *)passphrase),
&salt, sizeof (uint64_t), (uint8_t *)key->value,
WRAPPING_KEY_LEN, NULL, 0, Argon2_id, ARGON2_VERSION_13)
!= ARGON2_OK)
errstr = "ARGON2ID13 failed";
break;
default:
errstr = "unknown KDF";
break;
}
if (errstr)
goto err;
return (key);

err:
pam_syslog(pamh, LOG_ERR, "%s", errstr);
pw_free(key);
return (NULL);
}

static int
Expand Down
4 changes: 2 additions & 2 deletions contrib/pyzfs/libzfs_core/_libzfs_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -836,8 +836,8 @@ def lzc_change_key(fsname, crypt_cmd, props=None, key=None):
supported values are "new_key", "inherit", "force_new_key" and
"force_inherit".
:param props: a `dict` of encryption-related property name-value pairs;
only "keyformat", "keylocation" and "pbkdf2iters" are supported
(empty by default).
only "keyformat", "keylocation", "passphrasepkdf", and "pbkdf2iters"
are supported (empty by default).
:type props: dict of bytes:Any
:param key: dataset encryption key data (empty by default).
:type key: bytes
Expand Down
12 changes: 8 additions & 4 deletions include/sys/dsl_crypt.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@

/*
* ZAP entry keys for DSL Crypto Keys stored on disk. In addition,
* ZFS_PROP_KEYFORMAT, ZFS_PROP_PBKDF2_SALT, and ZFS_PROP_PBKDF2_ITERS are
* also maintained here using their respective property names.
* ZFS_PROP_KEYFORMAT, ZFS_PROP_PBKDF2_SALT, ZFS_PROP_PBKDF2_ITERS,
* and ZFS_PROP_PASSPHRASE_KDF are also maintained here using their
* respective property names.
*/
#define DSL_CRYPTO_KEY_CRYPTO_SUITE "DSL_CRYPTO_SUITE"
#define DSL_CRYPTO_KEY_GUID "DSL_CRYPTO_GUID"
Expand All @@ -52,10 +53,13 @@ typedef struct dsl_wrapping_key {
/* keyformat property enum */
zfs_keyformat_t wk_keyformat;

/* the pbkdf2 salt, if the keyformat is of type passphrase */
/* the KDF to use, if the keyformat is of type passphrase */
zfs_passphrase_kdf_t wk_kdf;

/* the KDF salt, if the keyformat is of type passphrase */
uint64_t wk_salt;

/* the pbkdf2 iterations, if the keyformat is of type passphrase */
/* the KDF iterations, if the keyformat is of type passphrase */
uint64_t wk_iters;

/* actual wrapping key */
Expand Down
35 changes: 32 additions & 3 deletions include/sys/fs/zfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ typedef enum {
ZFS_PROP_SNAPSHOTS_CHANGED,
ZFS_PROP_PREFETCH,
ZFS_PROP_VOLTHREADING,
ZFS_PROP_PASSPHRASE_KDF,
ZFS_NUM_PROPS
} zfs_prop_t;

Expand Down Expand Up @@ -539,6 +540,37 @@ typedef enum zfs_keyformat {
ZFS_KEYFORMAT_FORMATS
} zfs_keyformat_t;

typedef enum zfs_passphrase_kdf {
ZFS_PASSPHRASE_KDF_PBKDF2 = 0,
ZFS_PASSPHRASE_KDF_ARGON2ID,
ZFS_PASSPHRASE_KDF_KDFS
} zfs_passphrase_kdf_t;

typedef struct zfs_passphrase_kdf_argon2id_params {
uint32_t t_cost;
uint32_t m_cost;
uint32_t parallelism;
} zfs_passphrase_kdf_argon2id_params_t;

static zfs_passphrase_kdf_argon2id_params_t zfs_passphrase_kdf_argon2id_params(uint64_t iters) {
return (zfs_passphrase_kdf_argon2id_params_t){.t_cost = (iters >> (16 * 2)) & 0xFFFF,
.m_cost = (iters >> (16 * 1)) & 0xFFFF, .parallelism = (iters >> (16 * 0)) & 0xFFFF};
}

static boolean_t zfs_passphrase_kdf_pbkdf2_min_validate(uint64_t iters) {
return iters >= 100000;
}
static boolean_t zfs_passphrase_kdf_argon2id_min_validate(uint64_t iters) {
zfs_passphrase_kdf_argon2id_params_t p = zfs_passphrase_kdf_argon2id_params(iters);
return !(iters & 0xFFFF000000000000ull) && p.t_cost >= 1 && p.m_cost >= 12 && p.parallelism >= 1;
}

static const uint64_t zfs_passphrase_kdf_default_parameters[
ZFS_PASSPHRASE_KDF_KDFS] = {350000, 0x000100110001ull /*m=17*/};
static boolean_t(*const zfs_passphrase_kdf_min_parameters[
ZFS_PASSPHRASE_KDF_KDFS])(uint64_t) = {zfs_passphrase_kdf_pbkdf2_min_validate, zfs_passphrase_kdf_argon2id_min_validate};
static const char * const zfs_passphrase_kdf_min_parameters_str[ZFS_PASSPHRASE_KDF_KDFS] = {"100000", "0x0001000C0001"};

typedef enum zfs_key_location {
ZFS_KEYLOCATION_NONE = 0,
ZFS_KEYLOCATION_PROMPT,
Expand All @@ -552,9 +584,6 @@ typedef enum {
ZFS_PREFETCH_ALL = 2
} zfs_prefetch_type_t;

#define DEFAULT_PBKDF2_ITERATIONS 350000
#define MIN_PBKDF2_ITERATIONS 100000

/*
* On-disk version number.
*/
Expand Down
3 changes: 2 additions & 1 deletion lib/libzfs/libzfs.abi
Original file line number Diff line number Diff line change
Expand Up @@ -1822,7 +1822,8 @@
<enumerator name='ZFS_PROP_SNAPSHOTS_CHANGED' value='95'/>
<enumerator name='ZFS_PROP_PREFETCH' value='96'/>
<enumerator name='ZFS_PROP_VOLTHREADING' value='97'/>
<enumerator name='ZFS_NUM_PROPS' value='98'/>
<enumerator name='ZFS_PROP_PASSPHRASE_KDF' value='98'/>
<enumerator name='ZFS_NUM_PROPS' value='99'/>
</enum-decl>
<typedef-decl name='zfs_prop_t' type-id='4b000d60' id='58603c44'/>
<enum-decl name='zprop_source_t' naming-typedef-id='a2256d42' id='5903f80e'>
Expand Down
Loading

0 comments on commit 5153c92

Please sign in to comment.