diff --git a/.github/workflows/build-dependencies.txt b/.github/workflows/build-dependencies.txt index 73921865c42a..6a1332ab4ae3 100644 --- a/.github/workflows/build-dependencies.txt +++ b/.github/workflows/build-dependencies.txt @@ -28,6 +28,7 @@ libmount-dev libpam0g-dev libselinux1-dev libssl-dev +libargon2-dev libtool libudev-dev linux-headers-generic diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 777032bf82f6..742bd5e00650 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -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; @@ -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; diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 9939f206a7f2..c59b6a0a2219 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -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=]\n" - "\t [-o keylocation=] [-o pbkdf2iters=]\n" - "\t \n" + "\t [-o keylocation=] " + "[-o passphrasekdf=] \n" + "\t [-o pbkdf2iters=] \n" "\tchange-key -i [-l] \n")); case HELP_VERSION: return (gettext("\tversion\n")); diff --git a/cmd/ztest.c b/cmd/ztest.c index 1d414a9f6fd5..ed7375f8300e 100644 --- a/cmd/ztest.c +++ b/cmd/ztest.c @@ -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, diff --git a/config/user-libcrypto.m4 b/config/user-libcrypto.m4 index 7293e1b0b42c..5c66900fe83e 100644 --- a/config/user-libcrypto.m4 +++ b/config/user-libcrypto.m4 @@ -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, []) ]) diff --git a/contrib/pam_zfs_key/pam_zfs_key.c b/contrib/pam_zfs_key/pam_zfs_key.c index 08a8640669b3..d0e09c5f4c83 100644 --- a/contrib/pam_zfs_key/pam_zfs_key.c +++ b/contrib/pam_zfs_key/pam_zfs_key.c @@ -33,6 +33,7 @@ #include #include +#include #define PAM_SM_AUTH #define PAM_SM_PASSWORD @@ -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; @@ -270,8 +278,8 @@ 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; } @@ -279,34 +287,54 @@ prepare_passphrase(pam_handle_t *pamh, zfs_handle_t *ds, 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 diff --git a/contrib/pyzfs/libzfs_core/_libzfs_core.py b/contrib/pyzfs/libzfs_core/_libzfs_core.py index fa74ad9a760c..a8f369e74cba 100644 --- a/contrib/pyzfs/libzfs_core/_libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/_libzfs_core.py @@ -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 diff --git a/include/sys/dsl_crypt.h b/include/sys/dsl_crypt.h index fbcae3715355..6a9653705461 100644 --- a/include/sys/dsl_crypt.h +++ b/include/sys/dsl_crypt.h @@ -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" @@ -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 */ diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index c6f7dcca78b3..54d5330a019f 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -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; @@ -539,6 +540,52 @@ 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) +{ + struct zfs_passphrase_kdf_argon2id_params ret = { + .t_cost = (iters >> (16 * 2)) & 0xFFFF, + .m_cost = (iters >> (16 * 1)) & 0xFFFF, + .parallelism = (iters >> (16 * 0)) & 0xFFFF, + }; + return (ret); +} + +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, @@ -552,9 +599,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. */ diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 51b0368f8cf7..538a2933d873 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -1822,7 +1822,8 @@ - + + diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c index 8f2a50d55e87..7e8e71681763 100644 --- a/lib/libzfs/libzfs_crypto.c +++ b/lib/libzfs/libzfs_crypto.c @@ -26,6 +26,7 @@ #include #include #include +#include #if LIBFETCH_DYNAMIC #include #endif @@ -773,12 +774,14 @@ get_key_material(libzfs_handle_t *hdl, boolean_t do_verify, boolean_t newkey, } static int -derive_key(libzfs_handle_t *hdl, zfs_keyformat_t format, uint64_t iters, - uint8_t *key_material, uint64_t salt, - uint8_t **key_out) +derive_key(libzfs_handle_t *hdl, zfs_keyformat_t format, + zfs_passphrase_kdf_t kdf, uint64_t iters, uint8_t *key_material, + uint64_t salt, uint8_t **key_out) { int ret; uint8_t *key; + boolean_t err; + zfs_passphrase_kdf_argon2id_params_t params; *key_out = NULL; @@ -800,10 +803,28 @@ derive_key(libzfs_handle_t *hdl, zfs_keyformat_t format, uint64_t iters, case ZFS_KEYFORMAT_PASSPHRASE: salt = LE_64(salt); - ret = PKCS5_PBKDF2_HMAC_SHA1((char *)key_material, - strlen((char *)key_material), ((uint8_t *)&salt), - sizeof (uint64_t), iters, WRAPPING_KEY_LEN, key); - if (ret != 1) { + ret = 0; + switch (kdf) { + case ZFS_PASSPHRASE_KDF_PBKDF2: + err = PKCS5_PBKDF2_HMAC_SHA1((char *)key_material, + strlen((char *)key_material), ((uint8_t *)&salt), + sizeof (uint64_t), iters, WRAPPING_KEY_LEN, key) + != 1; + break; + case ZFS_PASSPHRASE_KDF_ARGON2ID: + params = zfs_passphrase_kdf_argon2id_params(iters); + err = argon2_hash(params.t_cost, params.m_cost, + params.parallelism, + key_material, strlen((char *)key_material), + &salt, sizeof (uint64_t), key, WRAPPING_KEY_LEN, + NULL, 0, Argon2_id, ARGON2_VERSION_13) + != ARGON2_OK; + break; + default: + ret = EINVAL; + goto error; + } + if (err) { ret = EIO; zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Failed to generate key from passphrase.")); @@ -849,8 +870,8 @@ encryption_feature_is_enabled(zpool_handle_t *zph) static int populate_create_encryption_params_nvlists(libzfs_handle_t *hdl, zfs_handle_t *zhp, boolean_t newkey, zfs_keyformat_t keyformat, - const char *keylocation, nvlist_t *props, uint8_t **wkeydata, - uint_t *wkeylen) + const char *keylocation, zfs_passphrase_kdf_t kdf, nvlist_t *props, + uint8_t **wkeydata, uint_t *wkeylen) { int ret; uint64_t iters = 0, salt = 0; @@ -892,7 +913,7 @@ populate_create_encryption_params_nvlists(libzfs_handle_t *hdl, ret = nvlist_lookup_uint64(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &iters); if (ret == ENOENT) { - iters = DEFAULT_PBKDF2_ITERATIONS; + iters = zfs_passphrase_kdf_default_parameters[kdf]; ret = nvlist_add_uint64(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), iters); if (ret != 0) @@ -916,7 +937,8 @@ populate_create_encryption_params_nvlists(libzfs_handle_t *hdl, } /* derive a key from the key material */ - ret = derive_key(hdl, keyformat, iters, key_material, salt, &key_data); + ret = derive_key(hdl, keyformat, kdf, iters, key_material, salt, + &key_data); if (ret != 0) goto error; @@ -954,15 +976,14 @@ proplist_has_encryption_props(nvlist_t *props) if (ret == 0 && strcmp(strval, "none") != 0) return (B_TRUE); - ret = nvlist_lookup_uint64(props, - zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &intval); - if (ret == 0) - return (B_TRUE); - - ret = nvlist_lookup_uint64(props, - zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &intval); - if (ret == 0) - return (B_TRUE); + zfs_prop_t exist_props[] = { + ZFS_PROP_KEYFORMAT, ZFS_PROP_PASSPHRASE_KDF, ZFS_PROP_PBKDF2_ITERS}; + for (size_t i = 0; i < ARRAY_SIZE(exist_props); ++i) { + ret = nvlist_lookup_uint64(props, + zfs_prop_to_name(exist_props[i]), &intval); + if (ret == 0) + return (B_TRUE); + } return (B_FALSE); } @@ -1007,6 +1028,7 @@ zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props, char errbuf[ERRBUFLEN]; uint64_t crypt = ZIO_CRYPT_INHERIT, pcrypt = ZIO_CRYPT_INHERIT; uint64_t keyformat = ZFS_KEYFORMAT_NONE; + uint64_t kdf = ZFS_PASSPHRASE_KDF_PBKDF2; const char *keylocation = NULL; zfs_handle_t *pzhp = NULL; uint8_t *wkeydata = NULL; @@ -1025,6 +1047,8 @@ zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props, /* lookup key location and format from props */ (void) nvlist_lookup_uint64(props, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat); + (void) nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), &kdf); (void) nvlist_lookup_string(props, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation); @@ -1146,7 +1170,7 @@ zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props, } ret = populate_create_encryption_params_nvlists(hdl, NULL, - B_TRUE, keyformat, keylocation, props, &wkeydata, + B_TRUE, keyformat, keylocation, kdf, props, &wkeydata, &wkeylen); if (ret != 0) goto out; @@ -1185,6 +1209,7 @@ zfs_crypto_clone_check(libzfs_handle_t *hdl, zfs_handle_t *origin_zhp, * inherited from the origin dataset. */ if (nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_KEYFORMAT)) || + nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF)) || nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_KEYLOCATION)) || nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_ENCRYPTION)) || nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS))) { @@ -1279,6 +1304,7 @@ zfs_crypto_load_key(zfs_handle_t *zhp, boolean_t noop, int ret, attempts = 0; char errbuf[ERRBUFLEN]; uint64_t keystatus, iters = 0, salt = 0; + uint64_t kdf = ZFS_PASSPHRASE_KDF_PBKDF2; uint64_t keyformat = ZFS_KEYFORMAT_NONE; char prop_keylocation[MAXNAMELEN]; char prop_encroot[MAXNAMELEN]; @@ -1355,8 +1381,9 @@ zfs_crypto_load_key(zfs_handle_t *zhp, boolean_t noop, } } - /* passphrase formats require a salt and pbkdf2_iters property */ + /* passphrase formats require a kdf, salt, and pbkdf2_iters property */ if (keyformat == ZFS_KEYFORMAT_PASSPHRASE) { + kdf = zfs_prop_get_int(zhp, ZFS_PROP_PASSPHRASE_KDF); salt = zfs_prop_get_int(zhp, ZFS_PROP_PBKDF2_SALT); iters = zfs_prop_get_int(zhp, ZFS_PROP_PBKDF2_ITERS); } @@ -1373,8 +1400,8 @@ zfs_crypto_load_key(zfs_handle_t *zhp, boolean_t noop, goto error; /* derive a key from the key material */ - ret = derive_key(zhp->zfs_hdl, keyformat, iters, key_material, salt, - &key_data); + ret = derive_key(zhp->zfs_hdl, keyformat, kdf, iters, key_material, + salt, &key_data); if (ret != 0) goto error; @@ -1555,12 +1582,13 @@ zfs_crypto_verify_rewrap_nvlist(zfs_handle_t *zhp, nvlist_t *props, case ZFS_PROP_PBKDF2_ITERS: case ZFS_PROP_KEYFORMAT: case ZFS_PROP_KEYLOCATION: + case ZFS_PROP_PASSPHRASE_KDF: break; default: ret = EINVAL; zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, - "Only keyformat, keylocation and pbkdf2iters may " - "be set with this command.")); + "Only keyformat, keylocation, passphrasekdf and " + "pbkdf2iters may be set with this command.")); goto error; } } @@ -1587,13 +1615,14 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey) { int ret; char errbuf[ERRBUFLEN]; - boolean_t is_encroot; + boolean_t is_encroot, explicit_kdf; nvlist_t *props = NULL; uint8_t *wkeydata = NULL; uint_t wkeylen = 0; dcp_cmd_t cmd = (inheritkey) ? DCP_CMD_INHERIT : DCP_CMD_NEW_KEY; uint64_t crypt, pcrypt, keystatus, pkeystatus; uint64_t keyformat = ZFS_KEYFORMAT_NONE; + uint64_t kdf; zfs_handle_t *pzhp = NULL; const char *keylocation = NULL; char origin_name[MAXNAMELEN]; @@ -1658,6 +1687,8 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey) zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat); (void) nvlist_lookup_string(props, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation); + explicit_kdf = nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), &kdf) == 0; if (is_encroot) { /* @@ -1693,6 +1724,21 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey) keylocation = prop_keylocation; } + + if (!explicit_kdf) { + kdf = zfs_prop_get_int(zhp, + ZFS_PROP_PASSPHRASE_KDF); + ret = nvlist_add_uint64(props, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), + kdf); + if (ret != 0) { + zfs_error_aux(zhp->zfs_hdl, + dgettext(TEXT_DOMAIN, "Failed to " + "get existing passphrasekdf " + "property.")); + goto error; + } + } } else { /* need a new key for non-encryption roots */ if (keyformat == ZFS_KEYFORMAT_NONE) { @@ -1712,11 +1758,13 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey) if (ret != 0) goto error; } + + kdf = zfs_prop_get_int(zhp, ZFS_PROP_PASSPHRASE_KDF); } /* fetch the new wrapping key and associated properties */ ret = populate_create_encryption_params_nvlists(zhp->zfs_hdl, - zhp, B_TRUE, keyformat, keylocation, props, &wkeydata, + zhp, B_TRUE, keyformat, keylocation, kdf, props, &wkeydata, &wkeylen); if (ret != 0) goto error; diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index 727efc5a91ad..fdc580bbacaa 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -1035,6 +1035,9 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, int chosen_normal = -1; int chosen_utf = -1; int set_maxbs = 0; + zfs_passphrase_kdf_t chosen_kdf = ZFS_PASSPHRASE_KDF_PBKDF2; + uint64_t chosen_iters; + boolean_t have_chosen_iters = B_FALSE; if (nvlist_alloc(&ret, NV_UNIQUE_NAME, 0) != 0) { (void) no_memory(hdl); @@ -1503,14 +1506,13 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, } break; + case ZFS_PROP_PASSPHRASE_KDF: + chosen_kdf = intval; + break; + case ZFS_PROP_PBKDF2_ITERS: - if (intval < MIN_PBKDF2_ITERATIONS) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "minimum pbkdf2 iterations is %u"), - MIN_PBKDF2_ITERATIONS); - (void) zfs_error(hdl, EZFS_BADPROP, errbuf); - goto error; - } + chosen_iters = intval; + have_chosen_iters = B_TRUE; break; case ZFS_PROP_UTF8ONLY: @@ -1585,6 +1587,15 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, } } + if (have_chosen_iters && + !zfs_passphrase_kdf_min_parameters[chosen_kdf](chosen_iters)) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "minimum KDF iterations is %s"), + zfs_passphrase_kdf_min_parameters_str[chosen_kdf]); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + /* * If normalization was chosen, but no UTF8 choice was made, * enforce rejection of non-UTF8 names. diff --git a/man/man7/zfsprops.7 b/man/man7/zfsprops.7 index 9f2826c8a2e2..791eb59c1a78 100644 --- a/man/man7/zfsprops.7 +++ b/man/man7/zfsprops.7 @@ -38,7 +38,7 @@ .\" Copyright (c) 2019, Kjeld Schouten-Lebbing .\" Copyright (c) 2022 Hewlett Packard Enterprise Development LP. .\" -.Dd August 8, 2023 +.Dd February 8, 2024 .Dt ZFSPROPS 7 .Os . @@ -1110,7 +1110,7 @@ A raw key can be generated with the following command: .Dl # Nm dd Sy if=/dev/urandom bs=32 count=1 Sy of= Ns Pa /path/to/output/key .Pp Passphrases must be between 8 and 512 bytes long and will be processed through -PBKDF2 before being used (see the +a KDF before being used (see the .Sy pbkdf2iters property). Even though the encryption suite cannot be changed after dataset creation, @@ -1163,14 +1163,27 @@ and .Sy SSL_CLIENT_KEY_FILE environment variables can be set to configure the path to the client certificate and its key. +.It Sy passphrasekdf Ns = Ns Sy pbkdf2 Ns | Ns Sy argon2id +If keyformat is +.Sy passphrase , +this variable governs which key derivation function will be used (see below). +The default value is +.Sy pbkdf2 +(PBKDF2). +Datasets using +.Sy argon2id +(Argon2id version 13) +cannot be unlocked with OpenZFS <2.2.3. +This property may be changed with +.Nm zfs Cm change-key . .It Sy pbkdf2iters Ns = Ns Ar iterations -Controls the number of PBKDF2 iterations that a +Controls KDF parameters that a .Sy passphrase encryption key should be run through when processing it into an encryption key. This property is only defined when encryption is enabled and a keyformat of .Sy passphrase is selected. -The goal of PBKDF2 is to significantly increase the +The goal of a KDF is to significantly increase the computational difficulty needed to brute force a user's passphrase. This is accomplished by forcing the attacker to run each passphrase through a computationally expensive hashing function many times before they arrive at the @@ -1178,12 +1191,24 @@ resulting key. A user who actually knows the passphrase will only have to pay this cost once. As CPUs become better at processing, this number should be raised to ensure that a brute force attack is still not possible. -The current default is -.Sy 350000 -and the minimum is -.Sy 100000 . This property may be changed with .Nm zfs Cm change-key . +.Pp +For PBKDF2, the only parameter is the iteration count (default +.Sy 350000 , +minimum +.Sy 10000 ) . +.br +For Argon2id, this parameter is a bitfield of three 16-bit integers: +the most significant is the iteration count, +then the memory cost (log2 kilobytes), +and the least significant is the parallelism. +The default is +.Sy 0x000100110001 +.Pq Va t Ns = Ns Sy 1 , Va m Ns = Ns Sy 17 Po 128M Pc , Va p Ns = Ns Sy 1 , +the minimum \(en +.Sy 0x0001000C0001 +.Pq Va t Ns = Ns Sy 1 , Va m Ns = Ns Sy 12 Po 4M Pc , Va p Ns = Ns Sy 1 . .It Sy exec Ns = Ns Sy on Ns | Ns Sy off Controls whether processes can be executed from within this file system. The default value is diff --git a/man/man8/zfs-allow.8 b/man/man8/zfs-allow.8 index d26984317c2e..9e78e720011e 100644 --- a/man/man8/zfs-allow.8 +++ b/man/man8/zfs-allow.8 @@ -29,7 +29,7 @@ .\" Copyright 2018 Nexenta Systems, Inc. .\" Copyright 2019 Joyent, Inc. .\" -.Dd March 16, 2022 +.Dd December 17, 2023 .Dt ZFS-ALLOW 8 .Os . @@ -249,6 +249,7 @@ filesystem_limit property fscontext property keyformat property keylocation property +passphrasekdf property logbias property mlslabel property mountpoint property diff --git a/man/man8/zfs-load-key.8 b/man/man8/zfs-load-key.8 index f68d6e985a78..4b535b6d7da4 100644 --- a/man/man8/zfs-load-key.8 +++ b/man/man8/zfs-load-key.8 @@ -29,7 +29,7 @@ .\" Copyright 2018 Nexenta Systems, Inc. .\" Copyright 2019 Joyent, Inc. .\" -.Dd January 13, 2020 +.Dd December 17, 2023 .Dt ZFS-LOAD-KEY 8 .Os . @@ -51,6 +51,7 @@ .Op Fl l .Op Fl o Ar keylocation Ns = Ns Ar value .Op Fl o Ar keyformat Ns = Ns Ar value +.Op Fl o Ar passphrasekdf Ns = Ns Ar value .Op Fl o Ar pbkdf2iters Ns = Ns Ar value .Ar filesystem .Nm zfs @@ -83,6 +84,9 @@ Note that if the is set to .Sy prompt the terminal will interactively wait for the key to be entered. +If +.Sy passphrasekdf +isn't specified, the current value is preserved. Loading a key will not automatically mount the dataset. If that functionality is desired, .Nm zfs Cm mount Fl l @@ -152,6 +156,7 @@ Unloads the keys for all encryption roots in all imported pools. .Op Fl l .Op Fl o Ar keylocation Ns = Ns Ar value .Op Fl o Ar keyformat Ns = Ns Ar value +.Op Fl o Ar passphrasekdf Ns = Ns Ar value .Op Fl o Ar pbkdf2iters Ns = Ns Ar value .Ar filesystem .Xc @@ -167,6 +172,7 @@ This command requires that the existing key for the dataset is already loaded. This command may also be used to change the .Sy keylocation , .Sy keyformat , +.Sy passphrasekdf , and .Sy pbkdf2iters properties as needed. @@ -204,10 +210,11 @@ This is effectively equivalent to running .Nm zfs Cm load-key Ar filesystem ; Nm zfs Cm change-key Ar filesystem .It Fl o Ar property Ns = Ns Ar value Allows the user to set encryption key properties -.Pq Sy keyformat , keylocation , No and Sy pbkdf2iters +.Pq Sy keyformat , keylocation , passphrasekdf , No and Sy pbkdf2iters while changing the key. This is the only way to alter -.Sy keyformat +.Sy keyformat , +.Sy passphrasekdf , and .Sy pbkdf2iters after the dataset has been created. @@ -244,7 +251,7 @@ subcommand for more info on key loading). Creating an encrypted dataset requires specifying the .Sy encryption No and Sy keyformat properties at creation time, along with an optional -.Sy keylocation No and Sy pbkdf2iters . +.Sy keylocation , passphrasekdf , No and Sy pbkdf2iters . After entering an encryption key, the created dataset will become an encryption root. Any descendant datasets will @@ -265,7 +272,7 @@ property alone does not create a new encryption root; this would simply use a different cipher suite with the same key as its encryption root. The one exception is that clones will always use their origin's encryption key. As a result of this exception, some encryption-related properties -.Pq namely Sy keystatus , keyformat , keylocation , No and Sy pbkdf2iters +.Pq namely Sy keystatus , keyformat , passphrasekdf , keylocation , No and Sy pbkdf2iters do not inherit like other ZFS properties and instead use the value determined by their encryption root. Encryption root inheritance can be tracked via the read-only diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 875c45ef662f..e811b9a7e043 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -234,6 +234,12 @@ zfs_prop_init(void) { NULL } }; + static const zprop_index_t passphrase_kdf_table[] = { + { "pbkdf2", ZFS_PASSPHRASE_KDF_PBKDF2 }, + { "argon2id", ZFS_PASSPHRASE_KDF_ARGON2ID }, + { NULL } + }; + static const zprop_index_t snapdir_table[] = { { "hidden", ZFS_SNAPDIR_HIDDEN }, { "visible", ZFS_SNAPDIR_VISIBLE }, @@ -554,6 +560,11 @@ zfs_prop_init(void) "on | off | aes-128-ccm | aes-192-ccm | aes-256-ccm | " "aes-128-gcm | aes-192-gcm | aes-256-gcm", "ENCRYPTION", crypto_table, sfeatures); + zprop_register_index(ZFS_PROP_PASSPHRASE_KDF, "passphrasekdf", + ZFS_PASSPHRASE_KDF_PBKDF2, PROP_ONETIME_DEFAULT, + ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, + "pbkdf2 | argon2id", "KDF", passphrase_kdf_table, + sfeatures); /* set once index (boolean) properties */ zprop_register_index(ZFS_PROP_UTF8ONLY, "utf8only", 0, PROP_ONETIME, @@ -972,7 +983,7 @@ zfs_prop_encryption_key_param(zfs_prop_t prop) * changed at will without needing the master keys. */ return (prop == ZFS_PROP_PBKDF2_SALT || prop == ZFS_PROP_PBKDF2_ITERS || - prop == ZFS_PROP_KEYFORMAT); + prop == ZFS_PROP_KEYFORMAT || prop == ZFS_PROP_PASSPHRASE_KDF); } /* diff --git a/module/zfs/dsl_crypt.c b/module/zfs/dsl_crypt.c index 8e1055d9bcb1..043377bca1cd 100644 --- a/module/zfs/dsl_crypt.c +++ b/module/zfs/dsl_crypt.c @@ -109,7 +109,8 @@ dsl_wrapping_key_free(dsl_wrapping_key_t *wkey) static void dsl_wrapping_key_create(uint8_t *wkeydata, zfs_keyformat_t keyformat, - uint64_t salt, uint64_t iters, dsl_wrapping_key_t **wkey_out) + zfs_passphrase_kdf_t kdf, uint64_t salt, uint64_t iters, + dsl_wrapping_key_t **wkey_out) { dsl_wrapping_key_t *wkey; @@ -125,6 +126,7 @@ dsl_wrapping_key_create(uint8_t *wkeydata, zfs_keyformat_t keyformat, /* initialize the rest of the struct */ zfs_refcount_create(&wkey->wk_refcnt); wkey->wk_keyformat = keyformat; + wkey->wk_kdf = kdf; wkey->wk_salt = salt; wkey->wk_iters = iters; @@ -138,6 +140,7 @@ dsl_crypto_params_create_nvlist(dcp_cmd_t cmd, nvlist_t *props, int ret; uint64_t crypt = ZIO_CRYPT_INHERIT; uint64_t keyformat = ZFS_KEYFORMAT_NONE; + uint64_t kdf = ZFS_PASSPHRASE_KDF_PBKDF2; uint64_t salt = 0, iters = 0; dsl_crypto_params_t *dcp = NULL; dsl_wrapping_key_t *wkey = NULL; @@ -156,6 +159,8 @@ dsl_crypto_params_create_nvlist(dcp_cmd_t cmd, nvlist_t *props, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat); (void) nvlist_lookup_string(props, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation); + (void) nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), &kdf); (void) nvlist_lookup_uint64(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), &salt); (void) nvlist_lookup_uint64(props, @@ -191,6 +196,12 @@ dsl_crypto_params_create_nvlist(dcp_cmd_t cmd, nvlist_t *props, goto error; } + /* check for valid kdf */ + if (kdf >= ZFS_PASSPHRASE_KDF_KDFS) { + ret = SET_ERROR(EINVAL); + goto error; + } + /* check for a valid keylocation (of any kind) and copy it in */ if (keylocation != NULL) { if (!zfs_prop_valid_keylocation(keylocation, B_FALSE)) { @@ -214,7 +225,7 @@ dsl_crypto_params_create_nvlist(dcp_cmd_t cmd, nvlist_t *props, /* create the wrapping key from the raw data */ if (wkeydata != NULL) { /* create the wrapping key with the verified parameters */ - dsl_wrapping_key_create(wkeydata, keyformat, salt, + dsl_wrapping_key_create(wkeydata, keyformat, kdf, salt, iters, &wkey); dcp->cp_wkey = wkey; } @@ -225,6 +236,8 @@ dsl_crypto_params_create_nvlist(dcp_cmd_t cmd, nvlist_t *props, */ (void) nvlist_remove_all(props, zfs_prop_to_name(ZFS_PROP_ENCRYPTION)); (void) nvlist_remove_all(props, zfs_prop_to_name(ZFS_PROP_KEYFORMAT)); + (void) nvlist_remove_all(props, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF)); (void) nvlist_remove_all(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT)); (void) nvlist_remove_all(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS)); @@ -776,7 +789,7 @@ spa_keystore_load_wkey(const char *dsname, dsl_crypto_params_t *dcp, dsl_crypto_key_t *dck = NULL; dsl_wrapping_key_t *wkey = dcp->cp_wkey; dsl_pool_t *dp = NULL; - uint64_t rddobj, keyformat, salt, iters; + uint64_t rddobj, keyformat, kdf, salt, iters; /* * We don't validate the wrapping key's keyformat, salt, or iters @@ -826,6 +839,13 @@ spa_keystore_load_wkey(const char *dsname, dsl_crypto_params_t *dcp, if (ret != 0) goto error; + ret = zap_lookup(dp->dp_meta_objset, dd->dd_crypto_obj, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), 8, 1, &kdf); + if (ret == ENOENT) + kdf = ZFS_PASSPHRASE_KDF_PBKDF2; + else if (ret != 0) + goto error; + ret = zap_lookup(dp->dp_meta_objset, dd->dd_crypto_obj, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), 8, 1, &salt); if (ret != 0) @@ -838,6 +858,7 @@ spa_keystore_load_wkey(const char *dsname, dsl_crypto_params_t *dcp, ASSERT3U(keyformat, <, ZFS_KEYFORMAT_FORMATS); ASSERT3U(keyformat, !=, ZFS_KEYFORMAT_NONE); + ASSERT3U(kdf, <, ZFS_PASSPHRASE_KDF_KDFS); IMPLY(keyformat == ZFS_KEYFORMAT_PASSPHRASE, iters != 0); IMPLY(keyformat == ZFS_KEYFORMAT_PASSPHRASE, salt != 0); IMPLY(keyformat != ZFS_KEYFORMAT_PASSPHRASE, iters == 0); @@ -1204,7 +1225,7 @@ static void dsl_crypto_key_sync_impl(objset_t *mos, uint64_t dckobj, uint64_t crypt, uint64_t root_ddobj, uint64_t guid, uint8_t *iv, uint8_t *mac, uint8_t *keydata, uint8_t *hmac_keydata, uint64_t keyformat, - uint64_t salt, uint64_t iters, dmu_tx_t *tx) + uint64_t kdf, uint64_t salt, uint64_t iters, dmu_tx_t *tx) { VERIFY0(zap_update(mos, dckobj, DSL_CRYPTO_KEY_CRYPTO_SUITE, 8, 1, &crypt, tx)); @@ -1222,6 +1243,9 @@ dsl_crypto_key_sync_impl(objset_t *mos, uint64_t dckobj, uint64_t crypt, SHA512_HMAC_KEYLEN, hmac_keydata, tx)); VERIFY0(zap_update(mos, dckobj, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), 8, 1, &keyformat, tx)); + VERIFY0(zap_update(mos, dckobj, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), + 8, 1, &kdf, tx)); VERIFY0(zap_update(mos, dckobj, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), 8, 1, &salt, tx)); VERIFY0(zap_update(mos, dckobj, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), @@ -1248,8 +1272,8 @@ dsl_crypto_key_sync(dsl_crypto_key_t *dck, dmu_tx_t *tx) /* update the ZAP with the obtained values */ dsl_crypto_key_sync_impl(tx->tx_pool->dp_meta_objset, dck->dck_obj, key->zk_crypt, wkey->wk_ddobj, key->zk_guid, iv, mac, keydata, - hmac_keydata, wkey->wk_keyformat, wkey->wk_salt, wkey->wk_iters, - tx); + hmac_keydata, wkey->wk_keyformat, wkey->wk_kdf, wkey->wk_salt, + wkey->wk_iters, tx); } typedef struct spa_keystore_change_key_args { @@ -1403,7 +1427,8 @@ spa_keystore_change_key_check(void *arg, dmu_tx_t *tx) /* passphrases require pbkdf2 salt and iters */ if (dcp->cp_wkey->wk_keyformat == ZFS_KEYFORMAT_PASSPHRASE) { if (dcp->cp_wkey->wk_salt == 0 || - dcp->cp_wkey->wk_iters < MIN_PBKDF2_ITERATIONS) { + !zfs_passphrase_kdf_min_parameters[ + dcp->cp_wkey->wk_kdf](dcp->cp_wkey->wk_iters)) { ret = SET_ERROR(EINVAL); goto error; } @@ -1889,7 +1914,8 @@ dmu_objset_create_crypt_check(dsl_dir_t *parentdd, dsl_crypto_params_t *dcp, case ZFS_KEYFORMAT_PASSPHRASE: /* requires pbkdf2 iters and salt */ if (dcp->cp_wkey->wk_salt == 0 || - dcp->cp_wkey->wk_iters < MIN_PBKDF2_ITERATIONS) + !zfs_passphrase_kdf_min_parameters[ + dcp->cp_wkey->wk_kdf](dcp->cp_wkey->wk_iters)) return (SET_ERROR(EINVAL)); break; case ZFS_KEYFORMAT_NONE: @@ -2244,6 +2270,13 @@ dsl_crypto_recv_raw_key_check(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx) is_passphrase = (intval == ZFS_KEYFORMAT_PASSPHRASE); + ret = nvlist_lookup_uint64(nvl, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), &intval); + if (ret == ENOENT) + ; + else if (ret != 0 || intval >= ZFS_PASSPHRASE_KDF_KDFS) + return (SET_ERROR(EINVAL)); + /* * for raw receives we allow any number of pbkdf2iters since there * won't be a chance for the user to change it. @@ -2271,6 +2304,7 @@ dsl_crypto_recv_raw_key_sync(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx) uint64_t rddobj, one = 1; uint8_t *keydata, *hmac_keydata, *iv, *mac; uint64_t crypt, key_guid, keyformat, iters, salt; + uint64_t kdf = ZFS_PASSPHRASE_KDF_PBKDF2; uint64_t version = ZIO_CRYPT_KEY_CURRENT_VERSION; const char *keylocation = "prompt"; @@ -2279,6 +2313,8 @@ dsl_crypto_recv_raw_key_sync(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx) key_guid = fnvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_GUID); keyformat = fnvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_KEYFORMAT)); + (void) nvlist_lookup_uint64(nvl, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), &kdf); iters = fnvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS)); salt = fnvlist_lookup_uint64(nvl, @@ -2331,8 +2367,8 @@ dsl_crypto_recv_raw_key_sync(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx) /* sync the key data to the ZAP object on disk */ dsl_crypto_key_sync_impl(mos, dd->dd_crypto_obj, crypt, - rddobj, key_guid, iv, mac, keydata, hmac_keydata, keyformat, salt, - iters, tx); + rddobj, key_guid, iv, mac, keydata, hmac_keydata, keyformat, kdf, + salt, iters, tx); } static int @@ -2426,6 +2462,7 @@ dsl_crypto_populate_key_nvlist(objset_t *os, uint64_t from_ivset_guid, dsl_pool_t *dp = ds->ds_dir->dd_pool; objset_t *mos = dp->dp_meta_objset; uint64_t crypt = 0, key_guid = 0, format = 0; + uint64_t kdf = ZFS_PASSPHRASE_KDF_PBKDF2; uint64_t iters = 0, salt = 0, version = 0; uint64_t to_ivset_guid = 0; uint8_t raw_keydata[MASTER_KEY_MAX_LEN]; @@ -2508,6 +2545,11 @@ dsl_crypto_populate_key_nvlist(objset_t *os, uint64_t from_ivset_guid, if (ret != 0) goto error_unlock; + ret = zap_lookup(dp->dp_meta_objset, rdd->dd_crypto_obj, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), 8, 1, &kdf); + if (ret != 0 && ret != ENOENT) + goto error_unlock; + if (format == ZFS_KEYFORMAT_PASSPHRASE) { ret = zap_lookup(dp->dp_meta_objset, rdd->dd_crypto_obj, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), 8, 1, &iters); @@ -2537,6 +2579,7 @@ dsl_crypto_populate_key_nvlist(objset_t *os, uint64_t from_ivset_guid, VERIFY0(nvlist_add_uint8_array(nvl, "portable_mac", os->os_phys->os_portable_mac, ZIO_OBJSET_MAC_LEN)); fnvlist_add_uint64(nvl, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), format); + fnvlist_add_uint64(nvl, zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), kdf); fnvlist_add_uint64(nvl, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), iters); fnvlist_add_uint64(nvl, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt); fnvlist_add_uint64(nvl, "mdn_checksum", mdn->dn_checksum); @@ -2650,6 +2693,10 @@ dsl_dataset_crypt_stats(dsl_dataset_t *ds, nvlist_t *nv) zfs_prop_to_name(ZFS_PROP_KEYFORMAT), 8, 1, &intval) == 0) { dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_KEYFORMAT, intval); } + if (zap_lookup(dd->dd_pool->dp_meta_objset, dd->dd_crypto_obj, + zfs_prop_to_name(ZFS_PROP_PASSPHRASE_KDF), 8, 1, &intval) == 0) { + dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_PASSPHRASE_KDF, intval); + } if (zap_lookup(dd->dd_pool->dp_meta_objset, dd->dd_crypto_obj, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), 8, 1, &intval) == 0) { dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_PBKDF2_SALT, intval); diff --git a/module/zfs/zcp_get.c b/module/zfs/zcp_get.c index 6fd45151d92a..76ee375d8567 100644 --- a/module/zfs/zcp_get.c +++ b/module/zfs/zcp_get.c @@ -378,13 +378,16 @@ get_special_prop(lua_State *state, dsl_dataset_t *ds, const char *dsname, } case ZFS_PROP_KEYSTATUS: - case ZFS_PROP_KEYFORMAT: { + case ZFS_PROP_KEYFORMAT: + case ZFS_PROP_PASSPHRASE_KDF: { /* provide defaults in case no crypto obj exists */ setpoint[0] = '\0'; if (zfs_prop == ZFS_PROP_KEYSTATUS) numval = ZFS_KEYSTATUS_NONE; - else + else if (zfs_prop == ZFS_PROP_KEYFORMAT) numval = ZFS_KEYFORMAT_NONE; + else + numval = ZFS_PASSPHRASE_KDF_PBKDF2; nvlist_t *nvl, *propval; nvl = fnvlist_alloc(); diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_change-key/zfs_change-key_pbkdf2iters.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_change-key/zfs_change-key_pbkdf2iters.ksh index 224fabf22620..aa0c3db78f6b 100755 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_change-key/zfs_change-key_pbkdf2iters.ksh +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_change-key/zfs_change-key_pbkdf2iters.ksh @@ -33,6 +33,8 @@ # 5. Unload the dataset's key # 6. Attempt to load the dataset's key # +# Do the same with Argon2id +# verify_runnable "both" @@ -58,6 +60,7 @@ log_onexit cleanup log_assert "'zfs change-key -o' should change the pbkdf2 iterations" + log_must eval "echo $PASSPHRASE > /$TESTPOOL/pkey" log_must zfs create -o encryption=on -o keyformat=passphrase \ -o keylocation=file:///$TESTPOOL/pkey -o pbkdf2iters=200000 \ @@ -72,4 +75,18 @@ log_must verify_pbkdf2iters $TESTPOOL/$TESTFS1 "150000" log_must zfs unload-key $TESTPOOL/$TESTFS1 log_must zfs load-key $TESTPOOL/$TESTFS1 + +log_must zfs change-key -o passphrasekdf=argon2id $TESTPOOL/$TESTFS1 +log_must verify_pbkdf2iters $TESTPOOL/$TESTFS1 "40000" + +log_must zfs unload-key $TESTPOOL/$TESTFS1 +log_must zfs load-key $TESTPOOL/$TESTFS1 + +log_must zfs change-key -o pbkdf2iters=10000 $TESTPOOL/$TESTFS1 +log_must verify_pbkdf2iters $TESTPOOL/$TESTFS1 "10000" + +log_must zfs unload-key $TESTPOOL/$TESTFS1 +log_must zfs load-key $TESTPOOL/$TESTFS1 + + log_pass "'zfs change-key -o' changes the pbkdf2 iterations"