diff --git a/Makefile.am b/Makefile.am
index 11e45dae8255..6b4995faa665 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -124,6 +124,7 @@ cstyle:
! -name 'zfs_config.*' ! -name '*.mod.c' \
! -name 'opt_global.h' ! -name '*_if*.h' \
! -name 'zstd_compat_wrapper.h' \
+ ! -name 'monocypher.[ch]' \
! -path './module/zstd/lib/*' \
! -path './include/sys/lua/*' \
! -path './module/lua/l*.[ch]' \
diff --git a/include/os/freebsd/zfs/sys/freebsd_crypto.h b/include/os/freebsd/zfs/sys/freebsd_crypto.h
index a61a6cd88c13..de8f35b5ca3d 100644
--- a/include/os/freebsd/zfs/sys/freebsd_crypto.h
+++ b/include/os/freebsd/zfs/sys/freebsd_crypto.h
@@ -41,6 +41,7 @@
#define SUN_CKM_AES_CCM "CKM_AES_CCM"
#define SUN_CKM_AES_GCM "CKM_AES_GCM"
#define SUN_CKM_SHA512_HMAC "CKM_SHA512_HMAC"
+#define SUN_CKM_CHACHA20_POLY1305 "CKM_CHACHA20_POLY1305"
#define CRYPTO_BITS2BYTES(n) ((n) == 0 ? 0 : (((n) - 1) >> 3) + 1)
#define CRYPTO_BYTES2BITS(n) ((n) << 3)
diff --git a/include/sys/crypto/common.h b/include/sys/crypto/common.h
index 261e88eceeea..a9715fcf31b5 100644
--- a/include/sys/crypto/common.h
+++ b/include/sys/crypto/common.h
@@ -109,6 +109,7 @@ typedef uint32_t crypto_keysize_unit_t;
#define SUN_CKM_AES_CCM "CKM_AES_CCM"
#define SUN_CKM_AES_GCM "CKM_AES_GCM"
#define SUN_CKM_AES_GMAC "CKM_AES_GMAC"
+#define SUN_CKM_CHACHA20_POLY1305 "CKM_CHACHA20_POLY1305"
/* Data arguments of cryptographic operations */
diff --git a/include/sys/crypto/icp.h b/include/sys/crypto/icp.h
index 8c3f19886fd8..63cb943e6462 100644
--- a/include/sys/crypto/icp.h
+++ b/include/sys/crypto/icp.h
@@ -26,6 +26,9 @@
#ifndef _SYS_CRYPTO_ALGS_H
#define _SYS_CRYPTO_ALGS_H
+int chapoly_mod_init(void);
+int chapoly_mod_fini(void);
+
int aes_mod_init(void);
int aes_mod_fini(void);
diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h
index 9d7eca9784b6..52e3ab40727b 100644
--- a/include/sys/fs/zfs.h
+++ b/include/sys/fs/zfs.h
@@ -1825,6 +1825,7 @@ enum zio_encrypt {
ZIO_CRYPT_AES_128_GCM,
ZIO_CRYPT_AES_192_GCM,
ZIO_CRYPT_AES_256_GCM,
+ ZIO_CRYPT_CHACHA20_POLY1305,
ZIO_CRYPT_FUNCTIONS
};
diff --git a/include/sys/zio_crypt.h b/include/sys/zio_crypt.h
index 6a3efabb0405..c8b50f53d32e 100644
--- a/include/sys/zio_crypt.h
+++ b/include/sys/zio_crypt.h
@@ -45,7 +45,8 @@ struct zbookmark_phys;
typedef enum zio_crypt_type {
ZC_TYPE_NONE = 0,
ZC_TYPE_CCM,
- ZC_TYPE_GCM
+ ZC_TYPE_GCM,
+ ZC_TYPE_CHACHA20_POLY1305,
} zio_crypt_type_t;
/* table of supported crypto algorithms, modes and keylengths. */
@@ -60,7 +61,7 @@ typedef struct zio_crypt_info {
#else
crypto_mech_name_t ci_mechname;
#endif
- /* cipher mode type (GCM, CCM) */
+ /* cipher mode type (GCM, CCM, ChaCha20-Poly1305) */
zio_crypt_type_t ci_crypt_type;
/* length of the encryption key */
diff --git a/include/zfeature_common.h b/include/zfeature_common.h
index 1025c44738ba..cfde1c76d7c6 100644
--- a/include/zfeature_common.h
+++ b/include/zfeature_common.h
@@ -81,6 +81,7 @@ typedef enum spa_feature {
SPA_FEATURE_BLOCK_CLONING,
SPA_FEATURE_AVZ_V2,
SPA_FEATURE_REDACTION_LIST_SPILL,
+ SPA_FEATURE_CHACHA20_POLY1305,
SPA_FEATURES
} spa_feature_t;
diff --git a/lib/libicp/Makefile.am b/lib/libicp/Makefile.am
index 4ba55b2158bc..0dbc446ebb1f 100644
--- a/lib/libicp/Makefile.am
+++ b/lib/libicp/Makefile.am
@@ -32,9 +32,11 @@ nodist_libicp_la_SOURCES = \
module/icp/algs/skein/skein_block.c \
module/icp/algs/skein/skein_iv.c \
module/icp/illumos-crypto.c \
+ module/icp/monocypher.c \
module/icp/io/aes.c \
module/icp/io/sha2_mod.c \
module/icp/io/skein_mod.c \
+ module/icp/io/chapoly.c \
module/icp/core/kcf_sched.c \
module/icp/core/kcf_prov_lib.c \
module/icp/core/kcf_callprov.c \
diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi
index 083c27be7d31..59e172d8af90 100644
--- a/lib/libzfs/libzfs.abi
+++ b/lib/libzfs/libzfs.abi
@@ -5822,7 +5822,8 @@
-
+
+
diff --git a/man/man7/zfsprops.7 b/man/man7/zfsprops.7
index 59f6404379af..02d02b64dca2 100644
--- a/man/man7/zfsprops.7
+++ b/man/man7/zfsprops.7
@@ -37,8 +37,9 @@
.\" Copyright 2019 Joyent, Inc.
.\" Copyright (c) 2019, Kjeld Schouten-Lebbing
.\" Copyright (c) 2022 Hewlett Packard Enterprise Development LP.
+.\" Copyright (c) 2023, Rob Norris
.\"
-.Dd August 8, 2023
+.Dd October 27, 2023
.Dt ZFSPROPS 7
.Os
.
@@ -1077,7 +1078,7 @@ This property can also be referred to by its shortened column name,
.It Xo
.Sy encryption Ns = Ns Sy off Ns | Ns Sy on Ns | Ns Sy aes-128-ccm Ns | Ns
.Sy aes-192-ccm Ns | Ns Sy aes-256-ccm Ns | Ns Sy aes-128-gcm Ns | Ns
-.Sy aes-192-gcm Ns | Ns Sy aes-256-gcm
+.Sy aes-192-gcm Ns | Ns Sy aes-256-gcm Ns | Ns Sy chacha20-poly1305
.Xc
Controls the encryption cipher suite (block cipher, key length, and mode) used
for this dataset.
@@ -1096,6 +1097,12 @@ selected, which is currently
In order to provide consistent data protection, encryption must be specified at
dataset creation time and it cannot be changed afterwards.
.Pp
+On systems lacking hardware-accelerated AES (many non-x86 boards)
+.Sy chacha20-poly1305
+will usually offer better performance without compromising security.
+On x86, or when datasets may be mounted on on older versions of ZFS, an AES
+suite is the best choice.
+.Pp
For more details and caveats about encryption see the
.Sx Encryption
section of
diff --git a/man/man7/zpool-features.7 b/man/man7/zpool-features.7
index 3c7b0b345d96..769a3233c3a1 100644
--- a/man/man7/zpool-features.7
+++ b/man/man7/zpool-features.7
@@ -17,8 +17,9 @@
.\" Copyright (c) 2019, Klara Inc.
.\" Copyright (c) 2019, Allan Jude
.\" Copyright (c) 2021, Colm Buckley
+.\" Copyright (c) 2023, Rob Norris
.\"
-.Dd June 23, 2022
+.Dd October 27, 2023
.Dt ZPOOL-FEATURES 7
.Os
.
@@ -398,6 +399,21 @@ returned to the
.Sy enabled
state when all bookmarks with these fields are destroyed.
.
+.feature org.openzfs chacha20_poly1305 no encryption extensible_dataset
+This feature enables the use of the ChaCha20-Poly1305 cipher suite for encrypted
+datasets.
+On systems lacking hardware-accelerated AES (many non-x86 boards), this suite
+will usually offer better performance than AES suites without compromising
+security.
+.Pp
+This feature becomes
+.Sy active
+when an encrypted dataset is created with
+.Nm encryption Ns = Ns Sy chacha20-poly1305
+and will be returned to the
+.Sy enabled
+state when all datasets that use this feature are destroyed.
+.
.feature org.openzfs device_rebuild yes
This feature enables the ability for the
.Nm zpool Cm attach
diff --git a/module/Kbuild.in b/module/Kbuild.in
index c132171592a8..11ac3a9abd3a 100644
--- a/module/Kbuild.in
+++ b/module/Kbuild.in
@@ -122,9 +122,11 @@ ICP_OBJS := \
core/kcf_prov_tabs.o \
core/kcf_sched.o \
illumos-crypto.o \
+ monocypher.o \
io/aes.o \
io/sha2_mod.o \
io/skein_mod.o \
+ io/chapoly.o \
spi/kcf_spi.o
ICP_OBJS_X86_64 := \
diff --git a/module/icp/illumos-crypto.c b/module/icp/illumos-crypto.c
index 13f05c06ed5c..a0c7490d0ecd 100644
--- a/module/icp/illumos-crypto.c
+++ b/module/icp/illumos-crypto.c
@@ -110,6 +110,7 @@ icp_fini(void)
skein_mod_fini();
sha2_mod_fini();
aes_mod_fini();
+ chapoly_mod_fini();
kcf_sched_destroy();
kcf_prov_tab_destroy();
kcf_destroy_mech_tabs();
@@ -132,6 +133,7 @@ icp_init(void)
kcf_sched_init();
/* initialize algorithms */
+ chapoly_mod_init();
aes_mod_init();
sha2_mod_init();
skein_mod_init();
diff --git a/module/icp/include/monocypher.h b/module/icp/include/monocypher.h
new file mode 100644
index 000000000000..4f361f885003
--- /dev/null
+++ b/module/icp/include/monocypher.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2017-2019, Loup Vaillant
+ * All rights reserved.
+ *
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Monocypher 4.0.2 (Poly1305, Chacha20, and supporting utilities)
+ * adapted for OpenZFS by Rob Norris
+ */
+
+/*
+ * Note: this follows the Monocypher style rather than the OpenZFS style to
+ * keep the diff to the bare minimum. This is important for making it easy to
+ * compare the two and confirm that they are in fact the same. The diff should
+ * be almost entirely in deleted lines.
+ */
+
+#ifndef MONOCYPHER_H
+#define MONOCYPHER_H
+
+#include
+
+
+// Constant time comparisons
+// -------------------------
+
+// Return 0 if a and b are equal, -1 otherwise
+int crypto_verify16(const uint8_t a[16], const uint8_t b[16]);
+
+// Erase sensitive data
+// --------------------
+void crypto_wipe(void *secret, size_t size);
+
+
+// Chacha20
+// --------
+
+// Unauthenticated stream cipher.
+// Don't forget to add authentication.
+uint32_t crypto_chacha20_ietf(uint8_t *cipher_text,
+ const uint8_t *plain_text,
+ size_t text_size,
+ const uint8_t key[32],
+ const uint8_t nonce[12],
+ uint32_t ctr);
+
+
+// Poly 1305
+// ---------
+
+// This is a *one time* authenticator.
+// Disclosing the mac reveals the key.
+
+// Incremental interface
+typedef struct {
+ // Do not rely on the size or contents of this type,
+ // for they may change without notice.
+ uint8_t c[16]; // chunk of the message
+ size_t c_idx; // How many bytes are there in the chunk.
+ uint32_t r [4]; // constant multiplier (from the secret key)
+ uint32_t pad[4]; // random number added at the end (from the secret key)
+ uint32_t h [5]; // accumulated hash
+} crypto_poly1305_ctx;
+
+void crypto_poly1305_init (crypto_poly1305_ctx *ctx, const uint8_t key[32]);
+void crypto_poly1305_update(crypto_poly1305_ctx *ctx,
+ const uint8_t *message, size_t message_size);
+void crypto_poly1305_final (crypto_poly1305_ctx *ctx, uint8_t mac[16]);
+
+#endif /* MONOCYPHER_H */
diff --git a/module/icp/io/chapoly.c b/module/icp/io/chapoly.c
new file mode 100644
index 000000000000..7c0fc7dd5c46
--- /dev/null
+++ b/module/icp/io/chapoly.c
@@ -0,0 +1,501 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or https://opensource.org/licenses/CDDL-1.0.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2023, Rob Norris
+ */
+
+/*
+ * ChaCha20-Poly1305 (RFC 8439) provider
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * convenient constants for readability. you can't change these; they're fixed
+ * to match defines and buffer sizes elsewhere.
+ */
+#define CP_BLOCK_SIZE (64)
+#define CP_KEY_SIZE (32)
+#define CP_MAC_SIZE (16)
+#define CP_IV_SIZE (12)
+
+typedef struct {
+ /* pointers back to the callers key and iv */
+ const uint8_t *key;
+ const uint8_t *iv;
+
+ /* poly1305 mac state */
+ crypto_poly1305_ctx poly;
+
+ /* counter value for next block */
+ uint32_t counter;
+
+ /* cipher output buffer and working space */
+ uint8_t temp[CP_BLOCK_SIZE];
+
+ /* bytes waiting for a complete block before they can be encrypted */
+ uint8_t pending[CP_BLOCK_SIZE];
+ size_t npending;
+
+ /* decrypt; data bytes remaining */
+ size_t datalen;
+
+ /* decrypt; pointer to pre-auth holding buffer */
+ /* (extra allocation past end of chapoly_ctx) */
+ uint8_t *unauthp;
+} chapoly_ctx;
+
+/* a bunch of zeroes for padding the poly1305 sequence */
+static const uint8_t zero_pad[16] = {0};
+
+static void
+chapoly_init(chapoly_ctx *cpctx, const crypto_key_t *key, const uint8_t *iv)
+{
+ cpctx->key = (const uint8_t *) key->ck_data;
+ cpctx->iv = (const uint8_t *) iv;
+
+ /* create the poly1305 key from the chacha block 0 keystream */
+ cpctx->counter = crypto_chacha20_ietf(
+ cpctx->temp, NULL, CP_KEY_SIZE,
+ cpctx->key, cpctx->iv, 0);
+
+ /* and intialise the context */
+ crypto_poly1305_init(&cpctx->poly, cpctx->temp);
+}
+
+
+static int
+chapoly_encrypt_contiguous_blocks(
+ void *_cpctx, char *data, size_t length, crypto_data_t *out)
+{
+ chapoly_ctx *cpctx = (chapoly_ctx *) _cpctx;
+
+ uint8_t *datap = (uint8_t *)data;
+ size_t nremaining = length;
+
+ size_t need;
+ int rv;
+
+ /* if there's anything in the pending buffer, try to empty it */
+ if (cpctx->npending > 0) {
+ /* take no more than we need to fill the temp buffer */
+ /* (one block), otherwise whatever is left */
+ need = nremaining > CP_BLOCK_SIZE - cpctx->npending ?
+ CP_BLOCK_SIZE - cpctx->npending : nremaining;
+
+ /* try fill that buffer */
+ memcpy(cpctx->pending + cpctx->npending, datap, need);
+ datap += need;
+ nremaining -= need;
+ cpctx->npending += need;
+
+ /* if we consumed everything and there's still not a full */
+ /* block then we've done all we can for now */
+ if (cpctx->npending < CP_BLOCK_SIZE) {
+ ASSERT0(nremaining);
+ return (CRYPTO_SUCCESS);
+ }
+
+ /* full block pending, process it */
+ cpctx->counter = crypto_chacha20_ietf(
+ cpctx->temp, cpctx->pending, CP_BLOCK_SIZE,
+ cpctx->key, cpctx->iv, cpctx->counter);
+
+ /* copy it to the output buffers */
+ rv = crypto_put_output_data(cpctx->temp, out, CP_BLOCK_SIZE);
+ if (rv != CRYPTO_SUCCESS)
+ return (rv);
+
+ /* update offset */
+ out->cd_offset += CP_BLOCK_SIZE;
+
+ /* update the mac */
+ crypto_poly1305_update(
+ &cpctx->poly, cpctx->temp, CP_BLOCK_SIZE);
+
+ /* pending buffer now drained */
+ cpctx->npending = 0;
+ }
+
+ /* process as many complete blocks as we can */
+ while (nremaining >= CP_BLOCK_SIZE) {
+
+ /* process one block */
+ cpctx->counter = crypto_chacha20_ietf(
+ cpctx->temp, datap, CP_BLOCK_SIZE,
+ cpctx->key, cpctx->iv, cpctx->counter);
+
+ /* copy it to the output buffers */
+ rv = crypto_put_output_data(cpctx->temp, out, CP_BLOCK_SIZE);
+ if (rv != CRYPTO_SUCCESS)
+ return (rv);
+
+ /* update offset */
+ out->cd_offset += CP_BLOCK_SIZE;
+
+ /* update the mac */
+ crypto_poly1305_update(
+ &cpctx->poly, cpctx->temp, CP_BLOCK_SIZE);
+
+ /* done a block */
+ datap += CP_BLOCK_SIZE;
+ nremaining -= CP_BLOCK_SIZE;
+ }
+
+ /* buffer anything left over for next time */
+ if (nremaining > 0) {
+ ASSERT3U(nremaining, <, CP_BLOCK_SIZE);
+
+ memcpy(cpctx->pending, datap, nremaining);
+ cpctx->npending = nremaining;
+ }
+
+ return (CRYPTO_SUCCESS);
+}
+
+static int
+chapoly_encrypt_atomic(crypto_mechanism_t *mechanism,
+ crypto_key_t *key, crypto_data_t *plaintext, crypto_data_t *ciphertext,
+ crypto_spi_ctx_template_t __attribute__((unused)) template)
+{
+ int rv;
+
+ /* We don't actually do GCM here, its just the default parameter */
+ /* option in zio_do_crypt_uio and has everything we need, so its */
+ /* easier to just take that instead of making our own thing. */
+ const CK_AES_GCM_PARAMS *gcmp =
+ (CK_AES_GCM_PARAMS *) mechanism->cm_param;
+ const uint8_t *iv = gcmp->pIv;
+
+ /* chacha20 invariants */
+ ASSERT3U(CRYPTO_BITS2BYTES(key->ck_length), ==, CP_KEY_SIZE);
+ ASSERT3U(gcmp->ulIvLen, ==, CP_IV_SIZE);
+
+ chapoly_ctx *cpctx = kmem_alloc(sizeof (chapoly_ctx), KM_SLEEP);
+ if (cpctx == NULL)
+ return (CRYPTO_HOST_MEMORY);
+ memset(cpctx, 0, sizeof (chapoly_ctx));
+
+ chapoly_init(cpctx, key, iv);
+
+ /* mix additional data into the mac */
+ crypto_poly1305_update(&cpctx->poly, gcmp->pAAD, gcmp->ulAADLen);
+ crypto_poly1305_update(
+ &cpctx->poly, zero_pad, (~(gcmp->ulAADLen) + 1) & 0xf);
+
+ off_t saved_offset = ciphertext->cd_offset;
+ size_t saved_length = ciphertext->cd_length;
+
+ switch (plaintext->cd_format) {
+ case CRYPTO_DATA_RAW:
+ rv = crypto_update_iov(
+ cpctx, plaintext, ciphertext,
+ chapoly_encrypt_contiguous_blocks);
+ break;
+ case CRYPTO_DATA_UIO:
+ rv = crypto_update_uio(
+ cpctx, plaintext, ciphertext,
+ chapoly_encrypt_contiguous_blocks);
+ break;
+ default:
+ rv = CRYPTO_ARGUMENTS_BAD;
+ }
+
+ if (rv == CRYPTO_SUCCESS) {
+ /* process and emit anything in the pending buffer */
+ if (cpctx->npending > 0) {
+ crypto_chacha20_ietf(
+ cpctx->temp, cpctx->pending, cpctx->npending,
+ cpctx->key, cpctx->iv, cpctx->counter);
+
+ /* write the last bit of the ciphertext */
+ rv = crypto_put_output_data(
+ cpctx->temp, ciphertext, cpctx->npending);
+ if (rv != CRYPTO_SUCCESS)
+ goto out;
+ ciphertext->cd_offset += cpctx->npending;
+
+ /* and update the mac */
+ crypto_poly1305_update(
+ &cpctx->poly, cpctx->temp, cpctx->npending);
+ }
+
+ /* finish the mac */
+ uint64_t sizes[2] = {
+ LE_64(gcmp->ulAADLen), LE_64(plaintext->cd_length)
+ };
+ crypto_poly1305_update(
+ &cpctx->poly, zero_pad,
+ (~(plaintext->cd_length) + 1) & 0xf);
+ crypto_poly1305_update(&cpctx->poly, (uint8_t *)sizes, 16);
+ crypto_poly1305_final(&cpctx->poly, cpctx->temp);
+
+ /* and write it out */
+ rv = crypto_put_output_data(
+ cpctx->temp, ciphertext, CP_MAC_SIZE);
+ if (rv != CRYPTO_SUCCESS)
+ goto out;
+ ciphertext->cd_offset += CP_MAC_SIZE;
+
+ ciphertext->cd_length = ciphertext->cd_offset - saved_offset;
+ }
+ else
+ ciphertext->cd_length = saved_length;
+ ciphertext->cd_offset = saved_offset;
+
+out:
+ crypto_wipe(cpctx, sizeof (chapoly_ctx));
+ kmem_free(cpctx, sizeof (chapoly_ctx));
+ return (rv);
+}
+
+
+static int
+chapoly_decrypt_contiguous_blocks(
+ void *_cpctx, char *data, size_t length,
+ crypto_data_t __attribute__((unused)) *out)
+{
+ chapoly_ctx *cpctx = (chapoly_ctx *) _cpctx;
+ size_t need;
+
+ if (cpctx->datalen > 0) {
+ /* these are data bytes */
+
+ /* don't take more than we need; the mac might be on the end */
+ need = length > cpctx->datalen ? cpctx->datalen : length;
+
+ /* copy the ciphertext to a buffer we made for it */
+ memcpy(cpctx->unauthp, data, need);
+ cpctx->unauthp += need;
+ cpctx->datalen -= need;
+
+ /* update the mac */
+ crypto_poly1305_update(&cpctx->poly, (uint8_t *)data, need);
+
+ /* update how much we're still expecting */
+ length -= need;
+ data += need;
+
+ /* if we consumed the whole buffer, we're done */
+ if (length == 0)
+ return (CRYPTO_SUCCESS);
+ }
+
+ /* these are mac bytes */
+
+ /* assume that the mac always arrives in a single block, not split */
+ /* over blocks. this is true for OpenZFS at least */
+ if (length != CP_MAC_SIZE)
+ return (CRYPTO_DATA_LEN_RANGE);
+
+ /* leave the incoming mac in the temp buffer */
+ memcpy(cpctx->temp, data, 16);
+
+ return (CRYPTO_SUCCESS);
+}
+
+static int
+chapoly_decrypt_finish(
+ chapoly_ctx *cpctx, size_t length, crypto_data_t *out)
+{
+ uint8_t *datap = (uint8_t *)cpctx + sizeof (chapoly_ctx);
+ size_t nremaining = length;
+
+ size_t need;
+
+ int rv;
+
+ while (nremaining > 0) {
+ /* take no more than we need to fill the temp buffer */
+ /* (one block), otherwise whatever is left */
+ need = nremaining > CP_BLOCK_SIZE ? CP_BLOCK_SIZE : nremaining;
+
+ /* process a block */
+ cpctx->counter = crypto_chacha20_ietf(
+ cpctx->temp, datap, need,
+ cpctx->key, cpctx->iv, cpctx->counter);
+
+ /* copy it into the output buffers */
+ rv = crypto_put_output_data(cpctx->temp, out, need);
+ if (rv != CRYPTO_SUCCESS)
+ return (rv);
+
+ /* update offset */
+ out->cd_offset += need;
+
+ /* update remaining */
+ nremaining -= need;
+ datap += need;
+ }
+
+ return (CRYPTO_SUCCESS);
+}
+
+static int
+chapoly_decrypt_atomic(crypto_mechanism_t *mechanism,
+ crypto_key_t *key, crypto_data_t *ciphertext, crypto_data_t *plaintext,
+ crypto_spi_ctx_template_t __attribute__((unused)) template)
+{
+ int rv;
+
+ /* We don't actually do GCM here, its just the default parameter */
+ /* option in zio_do_crypt_uio and has everything we need, so its */
+ /* easier to just take that instead of making our own thing. */
+ const CK_AES_GCM_PARAMS *gcmp =
+ (CK_AES_GCM_PARAMS*) mechanism->cm_param;
+ const uint8_t *iv = gcmp->pIv;
+
+ /* chacha20 invariants */
+ ASSERT3U(CRYPTO_BITS2BYTES(key->ck_length), ==, CP_KEY_SIZE);
+ ASSERT3U(gcmp->ulIvLen, ==, CP_IV_SIZE);
+ ASSERT3U(CRYPTO_BITS2BYTES(gcmp->ulTagBits), ==, CP_MAC_SIZE);
+
+ size_t datalen = ciphertext->cd_length - CP_MAC_SIZE;
+
+ chapoly_ctx *cpctx = kmem_alloc(
+ sizeof (chapoly_ctx) + datalen, KM_SLEEP);
+ if (cpctx == NULL)
+ return (CRYPTO_HOST_MEMORY);
+ memset(cpctx, 0, sizeof (chapoly_ctx) + datalen);
+
+ chapoly_init(cpctx, key, iv);
+
+ /* mix additional data into the mac */
+ crypto_poly1305_update(&cpctx->poly, gcmp->pAAD, gcmp->ulAADLen);
+ crypto_poly1305_update(
+ &cpctx->poly, zero_pad, (~(gcmp->ulAADLen) + 1) & 0xf);
+
+ cpctx->datalen = datalen;
+ cpctx->unauthp = (uint8_t *)cpctx + sizeof (chapoly_ctx);
+
+ off_t saved_offset = plaintext->cd_offset;
+ size_t saved_length = plaintext->cd_length;
+
+ switch (ciphertext->cd_format) {
+ case CRYPTO_DATA_RAW:
+ rv = crypto_update_iov(
+ cpctx, ciphertext, plaintext,
+ chapoly_decrypt_contiguous_blocks);
+ break;
+ case CRYPTO_DATA_UIO:
+ rv = crypto_update_uio(
+ cpctx, ciphertext, plaintext,
+ chapoly_decrypt_contiguous_blocks);
+ break;
+ default:
+ rv = CRYPTO_ARGUMENTS_BAD;
+ }
+
+ if (rv == CRYPTO_SUCCESS) {
+ /* finish the mac. the incoming mac is at that start of the */
+ /* temp buffer, so we'll write the computed one after it */
+ uint64_t sizes[2] = {
+ LE_64(gcmp->ulAADLen), LE_64(datalen)
+ };
+ crypto_poly1305_update(
+ &cpctx->poly, zero_pad, (~(datalen) + 1) & 0xf);
+ crypto_poly1305_update(&cpctx->poly, (uint8_t *)sizes, 16);
+ crypto_poly1305_final(&cpctx->poly, cpctx->temp + CP_MAC_SIZE);
+
+ /* now compare them */
+ if (crypto_verify16(
+ cpctx->temp, cpctx->temp + CP_MAC_SIZE) != 0)
+ rv = CRYPTO_INVALID_MAC;
+ }
+
+ /* mac checks out; we're ready to decrypt */
+ if (rv == CRYPTO_SUCCESS)
+ /* mac has been checked, now we can decrypt */
+ rv = chapoly_decrypt_finish(cpctx, datalen, plaintext);
+
+ if (rv == CRYPTO_SUCCESS)
+ plaintext->cd_length = plaintext->cd_offset - saved_offset;
+ else
+ plaintext->cd_length = saved_length;
+ plaintext->cd_offset = saved_offset;
+
+ crypto_wipe(cpctx, sizeof (chapoly_ctx) + datalen);
+ kmem_free(cpctx, sizeof (chapoly_ctx) + datalen);
+ return (rv);
+}
+
+
+static const crypto_mech_info_t chapoly_mech_info_tab[] = {
+ {SUN_CKM_CHACHA20_POLY1305, 0,
+ CRYPTO_FG_ENCRYPT_ATOMIC | CRYPTO_FG_DECRYPT_ATOMIC },
+};
+
+static const crypto_cipher_ops_t chapoly_cipher_ops = {
+ .encrypt_init = NULL,
+ .encrypt = NULL,
+ .encrypt_update = NULL,
+ .encrypt_final = NULL,
+ .encrypt_atomic = chapoly_encrypt_atomic,
+ .decrypt_init = NULL,
+ .decrypt = NULL,
+ .decrypt_update = NULL,
+ .decrypt_final = NULL,
+ .decrypt_atomic = chapoly_decrypt_atomic
+};
+
+static const crypto_ops_t chapoly_crypto_ops = {
+ .co_digest_ops = NULL,
+ .co_cipher_ops = &chapoly_cipher_ops,
+ .co_mac_ops = NULL,
+ .co_ctx_ops = NULL,
+};
+
+static const crypto_provider_info_t chapoly_prov_info = {
+ "Chacha20-Poly1305 Software Provider",
+ &chapoly_crypto_ops,
+ sizeof (chapoly_mech_info_tab) / sizeof (crypto_mech_info_t),
+ chapoly_mech_info_tab
+};
+
+static crypto_kcf_provider_handle_t chapoly_prov_handle = 0;
+
+int
+chapoly_mod_init(void)
+{
+ /* Register with KCF. If the registration fails, remove the module. */
+ if (crypto_register_provider(&chapoly_prov_info, &chapoly_prov_handle))
+ return (EACCES);
+
+ return (0);
+}
+
+int
+chapoly_mod_fini(void)
+{
+ /* Unregister from KCF if module is registered */
+ if (chapoly_prov_handle != 0) {
+ if (crypto_unregister_provider(chapoly_prov_handle))
+ return (EBUSY);
+
+ chapoly_prov_handle = 0;
+ }
+
+ return (0);
+}
diff --git a/module/icp/monocypher.c b/module/icp/monocypher.c
new file mode 100644
index 000000000000..907508159cc8
--- /dev/null
+++ b/module/icp/monocypher.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2017-2019, Loup Vaillant
+ * All rights reserved.
+ *
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Monocypher 4.0.2 (Poly1305, Chacha20, and supporting utilities)
+ * adapted for OpenZFS by Rob Norris
+ */
+
+/*
+ * Note: this follows the Monocypher style rather than the OpenZFS style to
+ * keep the diff to the bare minimum. This is important for making it easy to
+ * compare the two and confirm that they are in fact the same. The diff should
+ * be almost entirely in deleted lines.
+ */
+
+#include "monocypher.h"
+
+/////////////////
+/// Utilities ///
+/////////////////
+#define FOR_T(type, i, start, end) for (type i = (start); i < (end); i++)
+#define FOR(i, start, end) FOR_T(size_t, i, start, end)
+#define ZERO(buf, size) FOR(_i_, 0, size) (buf)[_i_] = 0
+#define WIPE_CTX(ctx) crypto_wipe(ctx , sizeof(*(ctx)))
+#define WIPE_BUFFER(buffer) crypto_wipe(buffer, sizeof(buffer))
+
+/*
+ * OpenZFS: userspace libicp build on Linux will already have MIN/MAX defined
+ * through sys/types.h -> sys/param.h. Undefine them and let Monocypher use its
+ * own, in case they change in some important way in the future.
+ */
+#undef MIN
+#undef MAX
+#define MIN(a, b) ((a) <= (b) ? (a) : (b))
+#define MAX(a, b) ((a) >= (b) ? (a) : (b))
+
+typedef int8_t i8;
+typedef uint8_t u8;
+typedef int16_t i16;
+typedef uint32_t u32;
+typedef int32_t i32;
+typedef int64_t i64;
+typedef uint64_t u64;
+
+static const u8 zero[128] = {0};
+
+// returns the smallest positive integer y such that
+// (x + y) % pow_2 == 0
+// Basically, y is the "gap" missing to align x.
+// Only works when pow_2 is a power of 2.
+// Note: we use ~x+1 instead of -x to avoid compiler warnings
+static size_t gap(size_t x, size_t pow_2)
+{
+ return (~x + 1) & (pow_2 - 1);
+}
+
+static u32 load32_le(const u8 s[4])
+{
+ return
+ ((u32)s[0] << 0) |
+ ((u32)s[1] << 8) |
+ ((u32)s[2] << 16) |
+ ((u32)s[3] << 24);
+}
+
+static u64 load64_le(const u8 s[8])
+{
+ return load32_le(s) | ((u64)load32_le(s+4) << 32);
+}
+
+static void store32_le(u8 out[4], u32 in)
+{
+ out[0] = in & 0xff;
+ out[1] = (in >> 8) & 0xff;
+ out[2] = (in >> 16) & 0xff;
+ out[3] = (in >> 24) & 0xff;
+}
+
+static void load32_le_buf (u32 *dst, const u8 *src, size_t size) {
+ FOR(i, 0, size) { dst[i] = load32_le(src + i*4); }
+}
+
+static u32 rotl32(u32 x, u32 n) { return (x << n) ^ (x >> (32 - n)); }
+
+static int neq0(u64 diff)
+{
+ // constant time comparison to zero
+ // return diff != 0 ? -1 : 0
+ u64 half = (diff >> 32) | ((u32)diff);
+ return (1 & ((half - 1) >> 32)) - 1;
+}
+
+static u64 x16(const u8 a[16], const u8 b[16])
+{
+ return (load64_le(a + 0) ^ load64_le(b + 0))
+ | (load64_le(a + 8) ^ load64_le(b + 8));
+}
+int crypto_verify16(const u8 a[16], const u8 b[16]){ return neq0(x16(a, b)); }
+
+void crypto_wipe(void *secret, size_t size)
+{
+ volatile u8 *v_secret = (u8*)secret;
+ ZERO(v_secret, size);
+}
+
+/////////////////
+/// Chacha 20 ///
+/////////////////
+#define QUARTERROUND(a, b, c, d) \
+ a += b; d = rotl32(d ^ a, 16); \
+ c += d; b = rotl32(b ^ c, 12); \
+ a += b; d = rotl32(d ^ a, 8); \
+ c += d; b = rotl32(b ^ c, 7)
+
+static void chacha20_rounds(u32 out[16], const u32 in[16])
+{
+ // The temporary variables make Chacha20 10% faster.
+ u32 t0 = in[ 0]; u32 t1 = in[ 1]; u32 t2 = in[ 2]; u32 t3 = in[ 3];
+ u32 t4 = in[ 4]; u32 t5 = in[ 5]; u32 t6 = in[ 6]; u32 t7 = in[ 7];
+ u32 t8 = in[ 8]; u32 t9 = in[ 9]; u32 t10 = in[10]; u32 t11 = in[11];
+ u32 t12 = in[12]; u32 t13 = in[13]; u32 t14 = in[14]; u32 t15 = in[15];
+
+ FOR (i, 0, 10) { // 20 rounds, 2 rounds per loop.
+ QUARTERROUND(t0, t4, t8 , t12); // column 0
+ QUARTERROUND(t1, t5, t9 , t13); // column 1
+ QUARTERROUND(t2, t6, t10, t14); // column 2
+ QUARTERROUND(t3, t7, t11, t15); // column 3
+ QUARTERROUND(t0, t5, t10, t15); // diagonal 0
+ QUARTERROUND(t1, t6, t11, t12); // diagonal 1
+ QUARTERROUND(t2, t7, t8 , t13); // diagonal 2
+ QUARTERROUND(t3, t4, t9 , t14); // diagonal 3
+ }
+ out[ 0] = t0; out[ 1] = t1; out[ 2] = t2; out[ 3] = t3;
+ out[ 4] = t4; out[ 5] = t5; out[ 6] = t6; out[ 7] = t7;
+ out[ 8] = t8; out[ 9] = t9; out[10] = t10; out[11] = t11;
+ out[12] = t12; out[13] = t13; out[14] = t14; out[15] = t15;
+}
+
+static const u8 *chacha20_constant = (const u8*)"expand 32-byte k"; // 16 bytes
+
+static u64 crypto_chacha20_djb(u8 *cipher_text, const u8 *plain_text,
+ size_t text_size, const u8 key[32], const u8 nonce[8],
+ u64 ctr)
+{
+ u32 input[16];
+ load32_le_buf(input , chacha20_constant, 4);
+ load32_le_buf(input + 4, key , 8);
+ load32_le_buf(input + 14, nonce , 2);
+ input[12] = (u32) ctr;
+ input[13] = (u32)(ctr >> 32);
+
+ // Whole blocks
+ u32 pool[16];
+ size_t nb_blocks = text_size >> 6;
+ FOR (i, 0, nb_blocks) {
+ chacha20_rounds(pool, input);
+ if (plain_text != 0) {
+ FOR (j, 0, 16) {
+ u32 p = pool[j] + input[j];
+ store32_le(cipher_text, p ^ load32_le(plain_text));
+ cipher_text += 4;
+ plain_text += 4;
+ }
+ } else {
+ FOR (j, 0, 16) {
+ u32 p = pool[j] + input[j];
+ store32_le(cipher_text, p);
+ cipher_text += 4;
+ }
+ }
+ input[12]++;
+ if (input[12] == 0) {
+ input[13]++;
+ }
+ }
+ text_size &= 63;
+
+ // Last (incomplete) block
+ if (text_size > 0) {
+ if (plain_text == 0) {
+ plain_text = zero;
+ }
+ chacha20_rounds(pool, input);
+ u8 tmp[64];
+ FOR (i, 0, 16) {
+ store32_le(tmp + i*4, pool[i] + input[i]);
+ }
+ FOR (i, 0, text_size) {
+ cipher_text[i] = tmp[i] ^ plain_text[i];
+ }
+ WIPE_BUFFER(tmp);
+ }
+ ctr = input[12] + ((u64)input[13] << 32) + (text_size > 0);
+
+ WIPE_BUFFER(pool);
+ WIPE_BUFFER(input);
+ return ctr;
+}
+
+u32 crypto_chacha20_ietf(u8 *cipher_text, const u8 *plain_text,
+ size_t text_size,
+ const u8 key[32], const u8 nonce[12], u32 ctr)
+{
+ u64 big_ctr = ctr + ((u64)load32_le(nonce) << 32);
+ return (u32)crypto_chacha20_djb(cipher_text, plain_text, text_size,
+ key, nonce + 4, big_ctr);
+}
+
+/////////////////
+/// Poly 1305 ///
+/////////////////
+
+// h = (h + c) * r
+// preconditions:
+// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff
+// ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff
+// end <= 1
+// Postcondition:
+// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff
+static void poly_blocks(crypto_poly1305_ctx *ctx, const u8 *in,
+ size_t nb_blocks, unsigned end)
+{
+ // Local all the things!
+ const u32 r0 = ctx->r[0];
+ const u32 r1 = ctx->r[1];
+ const u32 r2 = ctx->r[2];
+ const u32 r3 = ctx->r[3];
+ const u32 rr0 = (r0 >> 2) * 5; // lose 2 bits...
+ const u32 rr1 = (r1 >> 2) + r1; // rr1 == (r1 >> 2) * 5
+ const u32 rr2 = (r2 >> 2) + r2; // rr1 == (r2 >> 2) * 5
+ const u32 rr3 = (r3 >> 2) + r3; // rr1 == (r3 >> 2) * 5
+ const u32 rr4 = r0 & 3; // ...recover 2 bits
+ u32 h0 = ctx->h[0];
+ u32 h1 = ctx->h[1];
+ u32 h2 = ctx->h[2];
+ u32 h3 = ctx->h[3];
+ u32 h4 = ctx->h[4];
+
+ FOR (i, 0, nb_blocks) {
+ // h + c, without carry propagation
+ const u64 s0 = (u64)h0 + load32_le(in); in += 4;
+ const u64 s1 = (u64)h1 + load32_le(in); in += 4;
+ const u64 s2 = (u64)h2 + load32_le(in); in += 4;
+ const u64 s3 = (u64)h3 + load32_le(in); in += 4;
+ const u32 s4 = h4 + end;
+
+ // (h + c) * r, without carry propagation
+ const u64 x0 = s0*r0+ s1*rr3+ s2*rr2+ s3*rr1+ s4*rr0;
+ const u64 x1 = s0*r1+ s1*r0 + s2*rr3+ s3*rr2+ s4*rr1;
+ const u64 x2 = s0*r2+ s1*r1 + s2*r0 + s3*rr3+ s4*rr2;
+ const u64 x3 = s0*r3+ s1*r2 + s2*r1 + s3*r0 + s4*rr3;
+ const u32 x4 = s4*rr4;
+
+ // partial reduction modulo 2^130 - 5
+ const u32 u5 = x4 + (x3 >> 32); // u5 <= 7ffffff5
+ const u64 u0 = (u5 >> 2) * 5 + (x0 & 0xffffffff);
+ const u64 u1 = (u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32);
+ const u64 u2 = (u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32);
+ const u64 u3 = (u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32);
+ const u32 u4 = (u3 >> 32) + (u5 & 3); // u4 <= 4
+
+ // Update the hash
+ h0 = u0 & 0xffffffff;
+ h1 = u1 & 0xffffffff;
+ h2 = u2 & 0xffffffff;
+ h3 = u3 & 0xffffffff;
+ h4 = u4;
+ }
+ ctx->h[0] = h0;
+ ctx->h[1] = h1;
+ ctx->h[2] = h2;
+ ctx->h[3] = h3;
+ ctx->h[4] = h4;
+}
+
+void crypto_poly1305_init(crypto_poly1305_ctx *ctx, const u8 key[32])
+{
+ ZERO(ctx->h, 5); // Initial hash is zero
+ ctx->c_idx = 0;
+ // load r and pad (r has some of its bits cleared)
+ load32_le_buf(ctx->r , key , 4);
+ load32_le_buf(ctx->pad, key+16, 4);
+ FOR (i, 0, 1) { ctx->r[i] &= 0x0fffffff; }
+ FOR (i, 1, 4) { ctx->r[i] &= 0x0ffffffc; }
+}
+
+void crypto_poly1305_update(crypto_poly1305_ctx *ctx,
+ const u8 *message, size_t message_size)
+{
+ // Avoid undefined NULL pointer increments with empty messages
+ if (message_size == 0) {
+ return;
+ }
+
+ // Align ourselves with block boundaries
+ size_t aligned = MIN(gap(ctx->c_idx, 16), message_size);
+ FOR (i, 0, aligned) {
+ ctx->c[ctx->c_idx] = *message;
+ ctx->c_idx++;
+ message++;
+ message_size--;
+ }
+
+ // If block is complete, process it
+ if (ctx->c_idx == 16) {
+ poly_blocks(ctx, ctx->c, 1, 1);
+ ctx->c_idx = 0;
+ }
+
+ // Process the message block by block
+ size_t nb_blocks = message_size >> 4;
+ poly_blocks(ctx, message, nb_blocks, 1);
+ message += nb_blocks << 4;
+ message_size &= 15;
+
+ // remaining bytes (we never complete a block here)
+ FOR (i, 0, message_size) {
+ ctx->c[ctx->c_idx] = message[i];
+ ctx->c_idx++;
+ }
+}
+
+void crypto_poly1305_final(crypto_poly1305_ctx *ctx, u8 mac[16])
+{
+ // Process the last block (if any)
+ // We move the final 1 according to remaining input length
+ // (this will add less than 2^130 to the last input block)
+ if (ctx->c_idx != 0) {
+ ZERO(ctx->c + ctx->c_idx, 16 - ctx->c_idx);
+ ctx->c[ctx->c_idx] = 1;
+ poly_blocks(ctx, ctx->c, 1, 0);
+ }
+
+ // check if we should subtract 2^130-5 by performing the
+ // corresponding carry propagation.
+ u64 c = 5;
+ FOR (i, 0, 4) {
+ c += ctx->h[i];
+ c >>= 32;
+ }
+ c += ctx->h[4];
+ c = (c >> 2) * 5; // shift the carry back to the beginning
+ // c now indicates how many times we should subtract 2^130-5 (0 or 1)
+ FOR (i, 0, 4) {
+ c += (u64)ctx->h[i] + ctx->pad[i];
+ store32_le(mac + i*4, (u32)c);
+ c = c >> 32;
+ }
+ WIPE_CTX(ctx);
+}
diff --git a/module/os/freebsd/zfs/crypto_os.c b/module/os/freebsd/zfs/crypto_os.c
index 1f139ea5b807..19174d837be5 100644
--- a/module/os/freebsd/zfs/crypto_os.c
+++ b/module/os/freebsd/zfs/crypto_os.c
@@ -305,6 +305,17 @@ freebsd_crypt_newsession(freebsd_crypt_session_t *sessp,
break;
}
break;
+ case ZC_TYPE_CHACHA20_POLY1305:
+ csp.csp_cipher_alg = CRYPTO_CHACHA20_POLY1305;
+ csp.csp_ivlen = CHACHA20_POLY1305_IV_LEN;
+ switch (key->ck_length/8) {
+ case CHACHA20_POLY1305_KEY:
+ break;
+ default:
+ error = EINVAL;
+ goto bad;
+ }
+ break;
default:
error = ENOTSUP;
goto bad;
@@ -453,6 +464,10 @@ freebsd_crypt_newsession(freebsd_crypt_session_t *sessp,
break;
}
break;
+ case ZC_TYPE_CHACHA20_POLY1305:
+ xform = &enc_xform_chacha20_poly1305;
+ xauth = &auth_hash_poly1305;
+ break;
default:
error = ENOTSUP;
goto bad;
@@ -555,6 +570,10 @@ freebsd_crypt_uio(boolean_t encrypt,
break;
}
break;
+ case ZC_TYPE_CHACHA20_POLY1305:
+ xform = &enc_xform_chacha20_poly1305;
+ xauth = &auth_hash_poly1305;
+ break;
default:
error = ENOTSUP;
goto bad;
diff --git a/module/os/freebsd/zfs/zio_crypt.c b/module/os/freebsd/zfs/zio_crypt.c
index fdbe13dbb5e9..ca8237aa20ac 100644
--- a/module/os/freebsd/zfs/zio_crypt.c
+++ b/module/os/freebsd/zfs/zio_crypt.c
@@ -194,15 +194,26 @@ typedef struct blkptr_auth_buf {
} blkptr_auth_buf_t;
const zio_crypt_info_t zio_crypt_table[ZIO_CRYPT_FUNCTIONS] = {
- {"", ZC_TYPE_NONE, 0, "inherit"},
- {"", ZC_TYPE_NONE, 0, "on"},
- {"", ZC_TYPE_NONE, 0, "off"},
- {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 16, "aes-128-ccm"},
- {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 24, "aes-192-ccm"},
- {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 32, "aes-256-ccm"},
- {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 16, "aes-128-gcm"},
- {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 24, "aes-192-gcm"},
- {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 32, "aes-256-gcm"}
+ {"", ZC_TYPE_NONE,
+ 0, "inherit"},
+ {"", ZC_TYPE_NONE,
+ 0, "on"},
+ {"", ZC_TYPE_NONE,
+ 0, "off"},
+ {SUN_CKM_AES_CCM, ZC_TYPE_CCM,
+ 16, "aes-128-ccm"},
+ {SUN_CKM_AES_CCM, ZC_TYPE_CCM,
+ 24, "aes-192-ccm"},
+ {SUN_CKM_AES_CCM, ZC_TYPE_CCM,
+ 32, "aes-256-ccm"},
+ {SUN_CKM_AES_GCM, ZC_TYPE_GCM,
+ 16, "aes-128-gcm"},
+ {SUN_CKM_AES_GCM, ZC_TYPE_GCM,
+ 24, "aes-192-gcm"},
+ {SUN_CKM_AES_GCM, ZC_TYPE_GCM,
+ 32, "aes-256-gcm"},
+ {SUN_CKM_CHACHA20_POLY1305, ZC_TYPE_CHACHA20_POLY1305,
+ 32, "chacha20-poly1305"},
};
static void
@@ -238,7 +249,8 @@ zio_crypt_key_init(uint64_t crypt, zio_crypt_key_t *key)
ci = &zio_crypt_table[crypt];
if (ci->ci_crypt_type != ZC_TYPE_GCM &&
- ci->ci_crypt_type != ZC_TYPE_CCM)
+ ci->ci_crypt_type != ZC_TYPE_CCM &&
+ ci->ci_crypt_type != ZC_TYPE_CHACHA20_POLY1305)
return (ENOTSUP);
keydata_len = zio_crypt_table[crypt].ci_keylen;
@@ -278,7 +290,8 @@ zio_crypt_key_init(uint64_t crypt, zio_crypt_key_t *key)
ci = &zio_crypt_table[crypt];
if (ci->ci_crypt_type != ZC_TYPE_GCM &&
- ci->ci_crypt_type != ZC_TYPE_CCM)
+ ci->ci_crypt_type != ZC_TYPE_CCM &&
+ ci->ci_crypt_type != ZC_TYPE_CHACHA20_POLY1305)
return (ENOTSUP);
ret = freebsd_crypt_newsession(&key->zk_session, ci,
@@ -400,7 +413,8 @@ zio_do_crypt_uio_opencrypto(boolean_t encrypt, freebsd_crypt_session_t *sess,
{
const zio_crypt_info_t *ci = &zio_crypt_table[crypt];
if (ci->ci_crypt_type != ZC_TYPE_GCM &&
- ci->ci_crypt_type != ZC_TYPE_CCM)
+ ci->ci_crypt_type != ZC_TYPE_CCM &&
+ ci->ci_crypt_type != ZC_TYPE_CHACHA20_POLY1305)
return (ENOTSUP);
diff --git a/module/os/linux/zfs/zio_crypt.c b/module/os/linux/zfs/zio_crypt.c
index 55554d09ee43..e0b5f9720668 100644
--- a/module/os/linux/zfs/zio_crypt.c
+++ b/module/os/linux/zfs/zio_crypt.c
@@ -195,15 +195,26 @@ typedef struct blkptr_auth_buf {
} blkptr_auth_buf_t;
const zio_crypt_info_t zio_crypt_table[ZIO_CRYPT_FUNCTIONS] = {
- {"", ZC_TYPE_NONE, 0, "inherit"},
- {"", ZC_TYPE_NONE, 0, "on"},
- {"", ZC_TYPE_NONE, 0, "off"},
- {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 16, "aes-128-ccm"},
- {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 24, "aes-192-ccm"},
- {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 32, "aes-256-ccm"},
- {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 16, "aes-128-gcm"},
- {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 24, "aes-192-gcm"},
- {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 32, "aes-256-gcm"}
+ {"", ZC_TYPE_NONE,
+ 0, "inherit"},
+ {"", ZC_TYPE_NONE,
+ 0, "on"},
+ {"", ZC_TYPE_NONE,
+ 0, "off"},
+ {SUN_CKM_AES_CCM, ZC_TYPE_CCM,
+ 16, "aes-128-ccm"},
+ {SUN_CKM_AES_CCM, ZC_TYPE_CCM,
+ 24, "aes-192-ccm"},
+ {SUN_CKM_AES_CCM, ZC_TYPE_CCM,
+ 32, "aes-256-ccm"},
+ {SUN_CKM_AES_GCM, ZC_TYPE_GCM,
+ 16, "aes-128-gcm"},
+ {SUN_CKM_AES_GCM, ZC_TYPE_GCM,
+ 24, "aes-192-gcm"},
+ {SUN_CKM_AES_GCM, ZC_TYPE_GCM,
+ 32, "aes-256-gcm"},
+ {SUN_CKM_CHACHA20_POLY1305, ZC_TYPE_CHACHA20_POLY1305,
+ 32, "chacha20-poly1305"},
};
void
diff --git a/module/zcommon/zfeature_common.c b/module/zcommon/zfeature_common.c
index 2c74d10f43ff..0977006c990b 100644
--- a/module/zcommon/zfeature_common.c
+++ b/module/zcommon/zfeature_common.c
@@ -749,6 +749,19 @@ zpool_feature_init(void)
redact_list_spill_deps, sfeatures);
}
+ {
+ static const spa_feature_t chapoly_deps[] = {
+ SPA_FEATURE_EXTENSIBLE_DATASET,
+ SPA_FEATURE_ENCRYPTION,
+ SPA_FEATURE_NONE
+ };
+ zfeature_register(SPA_FEATURE_CHACHA20_POLY1305,
+ "org.openzfs:chacha20_poly1305", "chacha20_poly1305",
+ "Chacha20-Poly1305 encryption suite.",
+ ZFEATURE_FLAG_PER_DATASET, ZFEATURE_TYPE_BOOLEAN,
+ chapoly_deps, sfeatures);
+ }
+
zfs_mod_list_supported_free(sfeatures);
}
diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c
index 29764674a31b..ff841d10af47 100644
--- a/module/zcommon/zfs_prop.c
+++ b/module/zcommon/zfs_prop.c
@@ -223,6 +223,7 @@ zfs_prop_init(void)
{ "aes-128-gcm", ZIO_CRYPT_AES_128_GCM },
{ "aes-192-gcm", ZIO_CRYPT_AES_192_GCM },
{ "aes-256-gcm", ZIO_CRYPT_AES_256_GCM },
+ { "chacha20-poly1305", ZIO_CRYPT_CHACHA20_POLY1305 },
{ NULL }
};
@@ -552,8 +553,8 @@ zfs_prop_init(void)
zprop_register_index(ZFS_PROP_ENCRYPTION, "encryption",
ZIO_CRYPT_DEFAULT, PROP_ONETIME, ZFS_TYPE_DATASET,
"on | off | aes-128-ccm | aes-192-ccm | aes-256-ccm | "
- "aes-128-gcm | aes-192-gcm | aes-256-gcm", "ENCRYPTION",
- crypto_table, sfeatures);
+ "aes-128-gcm | aes-192-gcm | aes-256-gcm | chacha20-poly1305",
+ "ENCRYPTION", crypto_table, sfeatures);
/* set once index (boolean) properties */
zprop_register_index(ZFS_PROP_UTF8ONLY, "utf8only", 0, PROP_ONETIME,
diff --git a/module/zfs/dsl_crypt.c b/module/zfs/dsl_crypt.c
index 5e6e4e3d6c39..9393bd4f45ea 100644
--- a/module/zfs/dsl_crypt.c
+++ b/module/zfs/dsl_crypt.c
@@ -1819,6 +1819,12 @@ dmu_objset_create_crypt_check(dsl_dir_t *parentdd, dsl_crypto_params_t *dcp,
return (SET_ERROR(EOPNOTSUPP));
}
+ if (crypt == ZIO_CRYPT_CHACHA20_POLY1305 && parentdd != NULL &&
+ !spa_feature_is_enabled(parentdd->dd_pool->dp_spa,
+ SPA_FEATURE_CHACHA20_POLY1305)) {
+ return (SET_ERROR(EOPNOTSUPP));
+ }
+
/* handle inheritance */
if (dcp->cp_wkey == NULL) {
ASSERT3P(parentdd, !=, NULL);
@@ -1937,6 +1943,9 @@ dsl_dataset_create_crypt_sync(uint64_t dsobj, dsl_dir_t *dd,
tx));
dsl_dataset_activate_feature(dsobj, SPA_FEATURE_ENCRYPTION,
(void *)B_TRUE, tx);
+ if (crypt == ZIO_CRYPT_CHACHA20_POLY1305)
+ dsl_dataset_activate_feature(dsobj,
+ SPA_FEATURE_CHACHA20_POLY1305, (void *)B_TRUE, tx);
/*
* If we inherited the wrapping key we release our reference now.
@@ -2157,6 +2166,11 @@ dsl_crypto_recv_raw_key_check(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx)
if (intval >= ZIO_CRYPT_FUNCTIONS)
return (SET_ERROR(ZFS_ERR_CRYPTO_NOTSUP));
+ if (intval == ZIO_CRYPT_CHACHA20_POLY1305 &&
+ !spa_feature_is_enabled(ds->ds_dir->dd_pool->dp_spa,
+ SPA_FEATURE_CHACHA20_POLY1305))
+ return (SET_ERROR(EOPNOTSUPP));
+
ret = nvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_GUID, &intval);
if (ret != 0)
return (SET_ERROR(EINVAL));
@@ -2276,6 +2290,13 @@ dsl_crypto_recv_raw_key_sync(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx)
SPA_FEATURE_ENCRYPTION, (void *)B_TRUE, tx);
ds->ds_feature[SPA_FEATURE_ENCRYPTION] = (void *)B_TRUE;
+ if (crypt == ZIO_CRYPT_CHACHA20_POLY1305) {
+ dsl_dataset_activate_feature(ds->ds_object,
+ SPA_FEATURE_CHACHA20_POLY1305, (void *)B_TRUE, tx);
+ ds->ds_feature[SPA_FEATURE_CHACHA20_POLY1305] =
+ (void *)B_TRUE;
+ }
+
/* save the dd_crypto_obj on disk */
VERIFY0(zap_add(mos, dd->dd_object, DD_FIELD_CRYPTO_KEY_OBJ,
sizeof (uint64_t), 1, &dd->dd_crypto_obj, tx));
diff --git a/module/zfs/dsl_pool.c b/module/zfs/dsl_pool.c
index 17b971248283..95a786bf62bc 100644
--- a/module/zfs/dsl_pool.c
+++ b/module/zfs/dsl_pool.c
@@ -537,8 +537,12 @@ dsl_pool_create(spa_t *spa, nvlist_t *zplprops __attribute__((unused)),
spa_feature_create_zap_objects(spa, tx);
if (dcp != NULL && dcp->cp_crypt != ZIO_CRYPT_OFF &&
- dcp->cp_crypt != ZIO_CRYPT_INHERIT)
+ dcp->cp_crypt != ZIO_CRYPT_INHERIT) {
spa_feature_enable(spa, SPA_FEATURE_ENCRYPTION, tx);
+ if (dcp->cp_crypt == ZIO_CRYPT_CHACHA20_POLY1305)
+ spa_feature_enable(spa,
+ SPA_FEATURE_CHACHA20_POLY1305, tx);
+ }
/* create the root dataset */
obj = dsl_dataset_create_sync_dd(dp->dp_root_dir, NULL, dcp, 0, tx);
diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run
index ef787c65c0f9..54ba35792e7f 100644
--- a/tests/runfiles/common.run
+++ b/tests/runfiles/common.run
@@ -243,7 +243,8 @@ tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos',
'zfs_receive_raw', 'zfs_receive_raw_incremental', 'zfs_receive_-e',
'zfs_receive_raw_-d', 'zfs_receive_from_zstd', 'zfs_receive_new_props',
'zfs_receive_-wR-encrypted-mix', 'zfs_receive_corrective',
- 'zfs_receive_compressed_corrective', 'zfs_receive_large_block_corrective']
+ 'zfs_receive_compressed_corrective', 'zfs_receive_large_block_corrective',
+ 'zfs_receive_chapoly_feature']
tags = ['functional', 'cli_root', 'zfs_receive']
[tests/functional/cli_root/zfs_rename]
diff --git a/tests/runfiles/sanity.run b/tests/runfiles/sanity.run
index ab41c05b8473..d3ac2be72a99 100644
--- a/tests/runfiles/sanity.run
+++ b/tests/runfiles/sanity.run
@@ -177,7 +177,8 @@ tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos',
'zfs_receive_016_pos', 'zfs_receive_from_encrypted',
'zfs_receive_to_encrypted', 'zfs_receive_raw',
'zfs_receive_raw_incremental', 'zfs_receive_-e',
- 'zfs_receive_raw_-d', 'zfs_receive_from_zstd', 'zfs_receive_new_props']
+ 'zfs_receive_raw_-d', 'zfs_receive_from_zstd', 'zfs_receive_new_props',
+ 'zfs_receive_chapoly_feature']
tags = ['functional', 'cli_root', 'zfs_receive']
[tests/functional/cli_root/zfs_rename]
diff --git a/tests/zfs-tests/Makefile.am b/tests/zfs-tests/Makefile.am
index f8166352489e..615cc1bf4311 100644
--- a/tests/zfs-tests/Makefile.am
+++ b/tests/zfs-tests/Makefile.am
@@ -21,6 +21,11 @@ scripts_zfs_tests_functional_tmpfile_PROGRAMS = \
%D%/tests/functional/tmpfile/tmpfile_003_pos \
%D%/tests/functional/tmpfile/tmpfile_stat_mode \
%D%/tests/functional/tmpfile/tmpfile_test
+
+scripts_zfs_tests_functional_chapolydir = $(datadir)/$(PACKAGE)/zfs-tests/tests/functional/chapoly
+scripts_zfs_tests_functional_chapoly_PROGRAMS = %D%/tests/functional/chapoly/chapoly_test
+%C%_tests_functional_chapoly_chapoly_test_LDADD = \
+ libzpool.la
endif
diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am
index 87b50f59ca7a..dddb884f8042 100644
--- a/tests/zfs-tests/tests/Makefile.am
+++ b/tests/zfs-tests/tests/Makefile.am
@@ -794,6 +794,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zfs_receive/zfs_receive_corrective.ksh \
functional/cli_root/zfs_receive/zfs_receive_compressed_corrective.ksh \
functional/cli_root/zfs_receive/zfs_receive_large_block_corrective.ksh \
+ functional/cli_root/zfs_receive/zfs_receive_chapoly_feature.ksh \
functional/cli_root/zfs_rename/cleanup.ksh \
functional/cli_root/zfs_rename/setup.ksh \
functional/cli_root/zfs_rename/zfs_rename_001_pos.ksh \
diff --git a/tests/zfs-tests/tests/functional/chapoly/.gitignore b/tests/zfs-tests/tests/functional/chapoly/.gitignore
new file mode 100644
index 000000000000..a05dad8409fd
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/chapoly/.gitignore
@@ -0,0 +1 @@
+chapoly_test
diff --git a/tests/zfs-tests/tests/functional/chapoly/chapoly_test.c b/tests/zfs-tests/tests/functional/chapoly/chapoly_test.c
new file mode 100644
index 000000000000..acfcdf95ea69
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/chapoly/chapoly_test.c
@@ -0,0 +1,814 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or https://opensource.org/licenses/CDDL-1.0.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2023, Rob Norris
+ */
+
+/*
+ * This program runs the test vectors from RFC 8439 to ensure the bundled
+ * implementations of Chacha20 and Poly1305 are hooked up properly, and then
+ * tests the Chacha20-Poly1305 ICP module to ensure it properly implements the
+ * AEAD.
+ *
+ * This is mostly useful to verify that alternate implementations of the
+ * algorithms (eg accelerated versions) do the right thing, as the
+ * implementations out there are highly variable in function and quality and
+ * its often very difficult to tell if they're producing the right results.
+ *
+ * That said, these tests passing doesn't say anything about the security
+ * characteristics of these algorithms as used in OpenZFS, only that the
+ * underlying implementations are probably not entirely broken.
+ */
+
+#include
+#include
+#include
+
+static void
+hexdump(const char *str, const uint8_t *src, uint_t len)
+{
+ printf("%12s:", str);
+ int i = 0;
+ while (i < len) {
+ if (i % 4 == 0)
+ printf(" ");
+ printf("%02x", src[i]);
+ i++;
+ if (i % 16 == 0 && i < len) {
+ printf("\n");
+ if (i < len)
+ printf(" ");
+ }
+ }
+ printf("\n");
+}
+
+static uint8_t PLAINTEXT_SUNSCREEN[] = {
+ 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, /* Ladies a */
+ 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, /* nd Gentl */
+ 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, /* emen of */
+ 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, /* the clas */
+ 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, /* s of '99 */
+ 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, /* : If I c */
+ 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, /* ould off */
+ 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, /* er you o */
+ 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, /* nly one */
+ 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, /* tip for */
+ 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, /* the futu */
+ 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, /* re, suns */
+ 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, /* creen wo */
+ 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, /* uld be i */
+ 0x74, 0x2e /* t. */
+};
+
+static uint8_t PLAINTEXT_IETF[] = {
+ 0x41, 0x6e, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6d, /* Any subm */
+ 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x74, /* ission t */
+ 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x45, /* o the IE */
+ 0x54, 0x46, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, /* TF inten */
+ 0x64, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, /* ded by t */
+ 0x68, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, /* he Contr */
+ 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x20, 0x66, /* ibutor f */
+ 0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, /* or publi */
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, /* cation a */
+ 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6f, 0x72, /* s all or */
+ 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, /* part of */
+ 0x20, 0x61, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, /* an IETF */
+ 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, /* Internet */
+ 0x74, 0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x20, /* -Draft */
+ 0x6f, 0x72, 0x20, 0x52, 0x46, 0x43, 0x20, 0x61, /* or RFC a */
+ 0x6e, 0x64, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, /* nd any s */
+ 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, /* tatement */
+ 0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x77, 0x69, /* made wi */
+ 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, /* thin the */
+ 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, /* context */
+ 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x49, /* of an IE */
+ 0x45, 0x54, 0x46, 0x20, 0x61, 0x63, 0x74, 0x69, /* TF acti */
+ 0x76, 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20, /* vity is */
+ 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, /* consider */
+ 0x65, 0x64, 0x20, 0x61, 0x6e, 0x20, 0x22, 0x49, /* ed an "I */
+ 0x45, 0x54, 0x46, 0x20, 0x43, 0x6f, 0x6e, 0x74, /* ETF Cont */
+ 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, /* ribution */
+ 0x22, 0x2e, 0x20, 0x53, 0x75, 0x63, 0x68, 0x20, /* ". Such */
+ 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, /* statemen */
+ 0x74, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, /* ts inclu */
+ 0x64, 0x65, 0x20, 0x6f, 0x72, 0x61, 0x6c, 0x20, /* de oral */
+ 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, /* statemen */
+ 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x45, /* ts in IE */
+ 0x54, 0x46, 0x20, 0x73, 0x65, 0x73, 0x73, 0x69, /* TF sessi */
+ 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x61, 0x73, 0x20, /* ons, as */
+ 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, 0x73, 0x20, /* well as */
+ 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, /* written */
+ 0x61, 0x6e, 0x64, 0x20, 0x65, 0x6c, 0x65, 0x63, /* and elec */
+ 0x74, 0x72, 0x6f, 0x6e, 0x69, 0x63, 0x20, 0x63, /* tronic c */
+ 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, /* ommunica */
+ 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x61, /* tions ma */
+ 0x64, 0x65, 0x20, 0x61, 0x74, 0x20, 0x61, 0x6e, /* de at an */
+ 0x79, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x6f, /* y time o */
+ 0x72, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2c, /* r place, */
+ 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, /* which ar */
+ 0x72, 0x65, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, /* e addre */
+ 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f /* ssed to */
+};
+
+static uint8_t PLAINTEXT_IETF2[] = {
+ 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, /* Internet */
+ 0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x20, /* -Drafts */
+ 0x61, 0x72, 0x65, 0x20, 0x64, 0x72, 0x61, 0x66, /* are draf */
+ 0x74, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, /* t docume */
+ 0x6e, 0x74, 0x73, 0x20, 0x76, 0x61, 0x6c, 0x69, /* nts vali */
+ 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, /* d for a */
+ 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20, /* maximum */
+ 0x6f, 0x66, 0x20, 0x73, 0x69, 0x78, 0x20, 0x6d, /* of six m */
+ 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x20, 0x61, 0x6e, /* onths an */
+ 0x64, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x62, 0x65, /* d may be */
+ 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, /* updated */
+ 0x2c, 0x20, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, /* , replac */
+ 0x65, 0x64, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x6f, /* ed, or o */
+ 0x62, 0x73, 0x6f, 0x6c, 0x65, 0x74, 0x65, 0x64, /* bsoleted */
+ 0x20, 0x62, 0x79, 0x20, 0x6f, 0x74, 0x68, 0x65, /* by othe */
+ 0x72, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, /* r docume */
+ 0x6e, 0x74, 0x73, 0x20, 0x61, 0x74, 0x20, 0x61, /* nts at a */
+ 0x6e, 0x79, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x2e, /* ny time. */
+ 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x69, /* It is i */
+ 0x6e, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x70, 0x72, /* nappropr */
+ 0x69, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, /* iate to */
+ 0x75, 0x73, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, /* use Inte */
+ 0x72, 0x6e, 0x65, 0x74, 0x2d, 0x44, 0x72, 0x61, /* rnet-Dra */
+ 0x66, 0x74, 0x73, 0x20, 0x61, 0x73, 0x20, 0x72, /* fts as r */
+ 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, /* eference */
+ 0x20, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, /* materia */
+ 0x6c, 0x20, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20, /* l or to */
+ 0x63, 0x69, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, /* cite the */
+ 0x6d, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, /* m other */
+ 0x74, 0x68, 0x61, 0x6e, 0x20, 0x61, 0x73, 0x20, /* than as */
+ 0x2f, 0xe2, 0x80, 0x9c, 0x77, 0x6f, 0x72, 0x6b, /* /...work */
+ 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x67, /* in prog */
+ 0x72, 0x65, 0x73, 0x73, 0x2e, 0x2f, 0xe2, 0x80, /* ress./.. */
+ 0x9d /* . */
+};
+
+static uint8_t PLAINTEXT_JABBERWOCKY[] = {
+ 0x27, 0x54, 0x77, 0x61, 0x73, 0x20, 0x62, 0x72, /* 'Twas br */
+ 0x69, 0x6c, 0x6c, 0x69, 0x67, 0x2c, 0x20, 0x61, /* illig, a */
+ 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, /* nd the s */
+ 0x6c, 0x69, 0x74, 0x68, 0x79, 0x20, 0x74, 0x6f, /* lithy to */
+ 0x76, 0x65, 0x73, 0x0a, 0x44, 0x69, 0x64, 0x20, /* ves.Did */
+ 0x67, 0x79, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, /* gyre and */
+ 0x20, 0x67, 0x69, 0x6d, 0x62, 0x6c, 0x65, 0x20, /* gimble */
+ 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, /* in the w */
+ 0x61, 0x62, 0x65, 0x3a, 0x0a, 0x41, 0x6c, 0x6c, /* abe:.All */
+ 0x20, 0x6d, 0x69, 0x6d, 0x73, 0x79, 0x20, 0x77, /* mimsy w */
+ 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, /* ere the */
+ 0x62, 0x6f, 0x72, 0x6f, 0x67, 0x6f, 0x76, 0x65, /* borogove */
+ 0x73, 0x2c, 0x0a, 0x41, 0x6e, 0x64, 0x20, 0x74, /* s,.And t */
+ 0x68, 0x65, 0x20, 0x6d, 0x6f, 0x6d, 0x65, 0x20, /* he mome */
+ 0x72, 0x61, 0x74, 0x68, 0x73, 0x20, 0x6f, 0x75, /* raths ou */
+ 0x74, 0x67, 0x72, 0x61, 0x62, 0x65, 0x2e /* tgrabe. */
+};
+
+static uint8_t PLAINTEXT_CFRG[] = {
+ 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x67, 0x72, /* Cryptogr */
+ 0x61, 0x70, 0x68, 0x69, 0x63, 0x20, 0x46, 0x6f, /* aphic Fo */
+ 0x72, 0x75, 0x6d, 0x20, 0x52, 0x65, 0x73, 0x65, /* rum Rese */
+ 0x61, 0x72, 0x63, 0x68, 0x20, 0x47, 0x72, 0x6f, /* arch Gro */
+ 0x75, 0x70 /* up */
+};
+
+typedef struct {
+ const char *name;
+ const uint8_t key[32];
+ const uint8_t nonce[12];
+ uint32_t counter;
+ const uint8_t *plaintext;
+ const uint8_t *ciphertext;
+ size_t textlen;
+} chacha_test_t;
+
+static const chacha_test_t chacha_tests[] = {
+{
+ .name = "RFC 8439 2.4.2",
+ .key = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
+ },
+ .nonce = {
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x4a,
+ 0x00, 0x00, 0x00, 0x00
+ },
+ .counter = 1,
+ .plaintext = PLAINTEXT_SUNSCREEN,
+ .ciphertext = (uint8_t *)&(uint8_t[]) {
+ 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80,
+ 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81,
+ 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2,
+ 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b,
+ 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab,
+ 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57,
+ 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab,
+ 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8,
+ 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61,
+ 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e,
+ 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06,
+ 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36,
+ 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6,
+ 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,
+ 0x87, 0x4d
+ },
+ .textlen = sizeof (PLAINTEXT_SUNSCREEN),
+}, {
+ .name = "RFC 8439 A.2 #1",
+ .key = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ },
+ .nonce = {
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ },
+ .counter = 0,
+ .plaintext = (uint8_t *)&(uint8_t[64]) { 0 },
+ .ciphertext = (uint8_t *)&(uint8_t[]) {
+ 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90,
+ 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28,
+ 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a,
+ 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7,
+ 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d,
+ 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37,
+ 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
+ 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86
+ },
+ .textlen = 64,
+}, {
+ .name = "RFC 8439 A.2 #2",
+ .key = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ },
+ .nonce = {
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02
+ },
+ .counter = 1,
+ .plaintext = PLAINTEXT_IETF,
+ .ciphertext = (uint8_t *)&(uint8_t[]) {
+ 0xa3, 0xfb, 0xf0, 0x7d, 0xf3, 0xfa, 0x2f, 0xde,
+ 0x4f, 0x37, 0x6c, 0xa2, 0x3e, 0x82, 0x73, 0x70,
+ 0x41, 0x60, 0x5d, 0x9f, 0x4f, 0x4f, 0x57, 0xbd,
+ 0x8c, 0xff, 0x2c, 0x1d, 0x4b, 0x79, 0x55, 0xec,
+ 0x2a, 0x97, 0x94, 0x8b, 0xd3, 0x72, 0x29, 0x15,
+ 0xc8, 0xf3, 0xd3, 0x37, 0xf7, 0xd3, 0x70, 0x05,
+ 0x0e, 0x9e, 0x96, 0xd6, 0x47, 0xb7, 0xc3, 0x9f,
+ 0x56, 0xe0, 0x31, 0xca, 0x5e, 0xb6, 0x25, 0x0d,
+ 0x40, 0x42, 0xe0, 0x27, 0x85, 0xec, 0xec, 0xfa,
+ 0x4b, 0x4b, 0xb5, 0xe8, 0xea, 0xd0, 0x44, 0x0e,
+ 0x20, 0xb6, 0xe8, 0xdb, 0x09, 0xd8, 0x81, 0xa7,
+ 0xc6, 0x13, 0x2f, 0x42, 0x0e, 0x52, 0x79, 0x50,
+ 0x42, 0xbd, 0xfa, 0x77, 0x73, 0xd8, 0xa9, 0x05,
+ 0x14, 0x47, 0xb3, 0x29, 0x1c, 0xe1, 0x41, 0x1c,
+ 0x68, 0x04, 0x65, 0x55, 0x2a, 0xa6, 0xc4, 0x05,
+ 0xb7, 0x76, 0x4d, 0x5e, 0x87, 0xbe, 0xa8, 0x5a,
+ 0xd0, 0x0f, 0x84, 0x49, 0xed, 0x8f, 0x72, 0xd0,
+ 0xd6, 0x62, 0xab, 0x05, 0x26, 0x91, 0xca, 0x66,
+ 0x42, 0x4b, 0xc8, 0x6d, 0x2d, 0xf8, 0x0e, 0xa4,
+ 0x1f, 0x43, 0xab, 0xf9, 0x37, 0xd3, 0x25, 0x9d,
+ 0xc4, 0xb2, 0xd0, 0xdf, 0xb4, 0x8a, 0x6c, 0x91,
+ 0x39, 0xdd, 0xd7, 0xf7, 0x69, 0x66, 0xe9, 0x28,
+ 0xe6, 0x35, 0x55, 0x3b, 0xa7, 0x6c, 0x5c, 0x87,
+ 0x9d, 0x7b, 0x35, 0xd4, 0x9e, 0xb2, 0xe6, 0x2b,
+ 0x08, 0x71, 0xcd, 0xac, 0x63, 0x89, 0x39, 0xe2,
+ 0x5e, 0x8a, 0x1e, 0x0e, 0xf9, 0xd5, 0x28, 0x0f,
+ 0xa8, 0xca, 0x32, 0x8b, 0x35, 0x1c, 0x3c, 0x76,
+ 0x59, 0x89, 0xcb, 0xcf, 0x3d, 0xaa, 0x8b, 0x6c,
+ 0xcc, 0x3a, 0xaf, 0x9f, 0x39, 0x79, 0xc9, 0x2b,
+ 0x37, 0x20, 0xfc, 0x88, 0xdc, 0x95, 0xed, 0x84,
+ 0xa1, 0xbe, 0x05, 0x9c, 0x64, 0x99, 0xb9, 0xfd,
+ 0xa2, 0x36, 0xe7, 0xe8, 0x18, 0xb0, 0x4b, 0x0b,
+ 0xc3, 0x9c, 0x1e, 0x87, 0x6b, 0x19, 0x3b, 0xfe,
+ 0x55, 0x69, 0x75, 0x3f, 0x88, 0x12, 0x8c, 0xc0,
+ 0x8a, 0xaa, 0x9b, 0x63, 0xd1, 0xa1, 0x6f, 0x80,
+ 0xef, 0x25, 0x54, 0xd7, 0x18, 0x9c, 0x41, 0x1f,
+ 0x58, 0x69, 0xca, 0x52, 0xc5, 0xb8, 0x3f, 0xa3,
+ 0x6f, 0xf2, 0x16, 0xb9, 0xc1, 0xd3, 0x00, 0x62,
+ 0xbe, 0xbc, 0xfd, 0x2d, 0xc5, 0xbc, 0xe0, 0x91,
+ 0x19, 0x34, 0xfd, 0xa7, 0x9a, 0x86, 0xf6, 0xe6,
+ 0x98, 0xce, 0xd7, 0x59, 0xc3, 0xff, 0x9b, 0x64,
+ 0x77, 0x33, 0x8f, 0x3d, 0xa4, 0xf9, 0xcd, 0x85,
+ 0x14, 0xea, 0x99, 0x82, 0xcc, 0xaf, 0xb3, 0x41,
+ 0xb2, 0x38, 0x4d, 0xd9, 0x02, 0xf3, 0xd1, 0xab,
+ 0x7a, 0xc6, 0x1d, 0xd2, 0x9c, 0x6f, 0x21, 0xba,
+ 0x5b, 0x86, 0x2f, 0x37, 0x30, 0xe3, 0x7c, 0xfd,
+ 0xc4, 0xfd, 0x80, 0x6c, 0x22, 0xf2, 0x21
+ },
+ .textlen = sizeof (PLAINTEXT_IETF),
+}, {
+ .name = "RFC 8439 A.2 #3",
+ .key = {
+ 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a,
+ 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0,
+ 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09,
+ 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0
+ },
+ .nonce = {
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02
+ },
+ .counter = 42,
+ .plaintext = PLAINTEXT_JABBERWOCKY,
+ .ciphertext = (uint8_t *)&(uint8_t[]) {
+ 0x62, 0xe6, 0x34, 0x7f, 0x95, 0xed, 0x87, 0xa4,
+ 0x5f, 0xfa, 0xe7, 0x42, 0x6f, 0x27, 0xa1, 0xdf,
+ 0x5f, 0xb6, 0x91, 0x10, 0x04, 0x4c, 0x0d, 0x73,
+ 0x11, 0x8e, 0xff, 0xa9, 0x5b, 0x01, 0xe5, 0xcf,
+ 0x16, 0x6d, 0x3d, 0xf2, 0xd7, 0x21, 0xca, 0xf9,
+ 0xb2, 0x1e, 0x5f, 0xb1, 0x4c, 0x61, 0x68, 0x71,
+ 0xfd, 0x84, 0xc5, 0x4f, 0x9d, 0x65, 0xb2, 0x83,
+ 0x19, 0x6c, 0x7f, 0xe4, 0xf6, 0x05, 0x53, 0xeb,
+ 0xf3, 0x9c, 0x64, 0x02, 0xc4, 0x22, 0x34, 0xe3,
+ 0x2a, 0x35, 0x6b, 0x3e, 0x76, 0x43, 0x12, 0xa6,
+ 0x1a, 0x55, 0x32, 0x05, 0x57, 0x16, 0xea, 0xd6,
+ 0x96, 0x25, 0x68, 0xf8, 0x7d, 0x3f, 0x3f, 0x77,
+ 0x04, 0xc6, 0xa8, 0xd1, 0xbc, 0xd1, 0xbf, 0x4d,
+ 0x50, 0xd6, 0x15, 0x4b, 0x6d, 0xa7, 0x31, 0xb1,
+ 0x87, 0xb5, 0x8d, 0xfd, 0x72, 0x8a, 0xfa, 0x36,
+ 0x75, 0x7a, 0x79, 0x7a, 0xc1, 0x88, 0xd1
+ },
+ .textlen = sizeof (PLAINTEXT_JABBERWOCKY),
+}, {
+ .name = NULL,
+} };
+
+static int
+test_chacha(void)
+{
+ uint8_t outbuf[1024];
+ int failed = 0;
+
+ for (int testno = 0; chacha_tests[testno].name; testno++) {
+ const chacha_test_t *test = &chacha_tests[testno];
+ printf("chacha test: %s: ", test->name);
+
+ crypto_chacha20_ietf(
+ outbuf, test->plaintext, test->textlen,
+ test->key, test->nonce, test->counter);
+
+ if (memcmp(outbuf, test->ciphertext, test->textlen) != 0) {
+ printf("FAIL\n");
+ printf(" ciphertexts don't match:\n");
+ hexdump("got", outbuf, test->textlen);
+ hexdump("expected", test->ciphertext, test->textlen);
+ failed |= 1;
+ }
+
+ else {
+ printf("SUCCESS\n");
+ }
+ }
+
+ return (failed);
+}
+
+typedef struct {
+ const char *name;
+ const uint8_t key[32];
+ const uint8_t *text;
+ size_t textlen;
+ const uint8_t tag[16];
+} poly_test_t;
+
+static const poly_test_t poly_tests[] = {
+{
+ .name = "RFC 8439 2.5.2",
+ .key = {
+ 0x85, 0xd6, 0xbe, 0x78, 0x57, 0x55, 0x6d, 0x33,
+ 0x7f, 0x44, 0x52, 0xfe, 0x42, 0xd5, 0x06, 0xa8,
+ 0x01, 0x03, 0x80, 0x8a, 0xfb, 0x0d, 0xb2, 0xfd,
+ 0x4a, 0xbf, 0xf6, 0xaf, 0x41, 0x49, 0xf5, 0x1b
+ },
+ .text = PLAINTEXT_CFRG,
+ .textlen = sizeof (PLAINTEXT_CFRG),
+ .tag = {
+ 0xa8, 0x06, 0x1d, 0xc1, 0x30, 0x51, 0x36, 0xc6,
+ 0xc2, 0x2b, 0x8b, 0xaf, 0x0c, 0x01, 0x27, 0xa9
+ },
+}, {
+ .name = "RFC 8439 A.3 #1",
+ .key = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ .text = (uint8_t *)&(uint8_t[]) {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ },
+ .textlen = 64,
+ .tag = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ },
+}, {
+ .name = "RFC 8439 A.3 #2",
+ .key = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x36, 0xe5, 0xf6, 0xb5, 0xc5, 0xe0, 0x60, 0x70,
+ 0xf0, 0xef, 0xca, 0x96, 0x22, 0x7a, 0x86, 0x3e
+ },
+ .text = PLAINTEXT_IETF,
+ .textlen = sizeof (PLAINTEXT_IETF),
+ .tag = {
+ 0x36, 0xe5, 0xf6, 0xb5, 0xc5, 0xe0, 0x60, 0x70,
+ 0xf0, 0xef, 0xca, 0x96, 0x22, 0x7a, 0x86, 0x3e
+ },
+}, {
+ .name = "RFC 8439 A.3 #3",
+ .key = {
+ 0x36, 0xe5, 0xf6, 0xb5, 0xc5, 0xe0, 0x60, 0x70,
+ 0xf0, 0xef, 0xca, 0x96, 0x22, 0x7a, 0x86, 0x3e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ },
+ .text = PLAINTEXT_IETF,
+ .textlen = sizeof (PLAINTEXT_IETF),
+ .tag = {
+ 0xf3, 0x47, 0x7e, 0x7c, 0xd9, 0x54, 0x17, 0xaf,
+ 0x89, 0xa6, 0xb8, 0x79, 0x4c, 0x31, 0x0c, 0xf0
+ },
+}, {
+ .name = "RFC 8439 A.3 #4",
+ .key = {
+ 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a,
+ 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0,
+ 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09,
+ 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0
+ },
+ .text = PLAINTEXT_JABBERWOCKY,
+ .textlen = sizeof (PLAINTEXT_JABBERWOCKY),
+ .tag = {
+ 0x45, 0x41, 0x66, 0x9a, 0x7e, 0xaa, 0xee, 0x61,
+ 0xe7, 0x08, 0xdc, 0x7c, 0xbc, 0xc5, 0xeb, 0x62
+ },
+}, {
+ .name = NULL,
+} };
+
+static int
+test_poly(void)
+{
+ uint8_t macbuf[16];
+ int failed = 0;
+
+ for (int testno = 0; poly_tests[testno].name; testno++) {
+ const poly_test_t *test = &poly_tests[testno];
+ printf("poly test: %s: ", test->name);
+
+ crypto_poly1305_ctx poly;
+ crypto_poly1305_init(&poly, test->key);
+
+ crypto_poly1305_update(&poly, test->text, test->textlen);
+
+ crypto_poly1305_final(&poly, macbuf);
+
+ if (memcmp(test->tag, macbuf, 16) != 0) {
+ printf("FAIL\n");
+ printf(" tags don't match:\n");
+ hexdump("got", macbuf, 16);
+ hexdump("expected", test->tag, 16);
+ failed |= 1;
+ }
+
+ else {
+ printf("SUCCESS\n");
+ }
+ }
+
+ return (failed);
+}
+
+typedef struct {
+ const char *name;
+ const uint8_t key[32];
+ const uint8_t nonce[12];
+ const uint8_t *aad;
+ size_t aadlen;
+ const uint8_t *plaintext;
+ const uint8_t *ciphertext;
+ size_t textlen;
+ const uint8_t *tag;
+ size_t taglen;
+} module_test_t;
+
+static const module_test_t module_tests[] = {
+{
+ .name = "RFC 8439 2.8.2",
+ .key = {
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f
+ },
+ .nonce = {
+ 0x07, 0x00, 0x00, 0x00,
+ 0x40, 0x41, 0x42, 0x43,
+ 0x44, 0x45, 0x46, 0x47
+ },
+ .aad = (uint8_t *)&(uint8_t[]) {
+ 0x50, 0x51, 0x52, 0x53,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7
+ },
+ .aadlen = 12,
+ .plaintext = PLAINTEXT_SUNSCREEN,
+ .ciphertext = (uint8_t *)&(uint8_t[]) {
+ 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb,
+ 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
+ 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe,
+ 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
+ 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12,
+ 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
+ 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29,
+ 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
+ 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c,
+ 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58,
+ 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94,
+ 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
+ 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d,
+ 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
+ 0x61, 0x16
+ },
+ .textlen = sizeof (PLAINTEXT_SUNSCREEN),
+ .tag = (uint8_t *)&(uint8_t[]) {
+ 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a,
+ 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91
+ },
+ .taglen = 16,
+}, {
+ .name = "RFC 8439 A.5",
+ .key = {
+ 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a,
+ 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0,
+ 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09,
+ 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0
+ },
+ .nonce = {
+ 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08
+ },
+ .aad = (uint8_t *)&(uint8_t[]) {
+ 0xf3, 0x33, 0x88, 0x86,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x91
+ },
+ .aadlen = 12,
+ .plaintext = PLAINTEXT_IETF2,
+ .ciphertext = (uint8_t *)&(uint8_t[]) {
+ 0x64, 0xa0, 0x86, 0x15, 0x75, 0x86, 0x1a, 0xf4,
+ 0x60, 0xf0, 0x62, 0xc7, 0x9b, 0xe6, 0x43, 0xbd,
+ 0x5e, 0x80, 0x5c, 0xfd, 0x34, 0x5c, 0xf3, 0x89,
+ 0xf1, 0x08, 0x67, 0x0a, 0xc7, 0x6c, 0x8c, 0xb2,
+ 0x4c, 0x6c, 0xfc, 0x18, 0x75, 0x5d, 0x43, 0xee,
+ 0xa0, 0x9e, 0xe9, 0x4e, 0x38, 0x2d, 0x26, 0xb0,
+ 0xbd, 0xb7, 0xb7, 0x3c, 0x32, 0x1b, 0x01, 0x00,
+ 0xd4, 0xf0, 0x3b, 0x7f, 0x35, 0x58, 0x94, 0xcf,
+ 0x33, 0x2f, 0x83, 0x0e, 0x71, 0x0b, 0x97, 0xce,
+ 0x98, 0xc8, 0xa8, 0x4a, 0xbd, 0x0b, 0x94, 0x81,
+ 0x14, 0xad, 0x17, 0x6e, 0x00, 0x8d, 0x33, 0xbd,
+ 0x60, 0xf9, 0x82, 0xb1, 0xff, 0x37, 0xc8, 0x55,
+ 0x97, 0x97, 0xa0, 0x6e, 0xf4, 0xf0, 0xef, 0x61,
+ 0xc1, 0x86, 0x32, 0x4e, 0x2b, 0x35, 0x06, 0x38,
+ 0x36, 0x06, 0x90, 0x7b, 0x6a, 0x7c, 0x02, 0xb0,
+ 0xf9, 0xf6, 0x15, 0x7b, 0x53, 0xc8, 0x67, 0xe4,
+ 0xb9, 0x16, 0x6c, 0x76, 0x7b, 0x80, 0x4d, 0x46,
+ 0xa5, 0x9b, 0x52, 0x16, 0xcd, 0xe7, 0xa4, 0xe9,
+ 0x90, 0x40, 0xc5, 0xa4, 0x04, 0x33, 0x22, 0x5e,
+ 0xe2, 0x82, 0xa1, 0xb0, 0xa0, 0x6c, 0x52, 0x3e,
+ 0xaf, 0x45, 0x34, 0xd7, 0xf8, 0x3f, 0xa1, 0x15,
+ 0x5b, 0x00, 0x47, 0x71, 0x8c, 0xbc, 0x54, 0x6a,
+ 0x0d, 0x07, 0x2b, 0x04, 0xb3, 0x56, 0x4e, 0xea,
+ 0x1b, 0x42, 0x22, 0x73, 0xf5, 0x48, 0x27, 0x1a,
+ 0x0b, 0xb2, 0x31, 0x60, 0x53, 0xfa, 0x76, 0x99,
+ 0x19, 0x55, 0xeb, 0xd6, 0x31, 0x59, 0x43, 0x4e,
+ 0xce, 0xbb, 0x4e, 0x46, 0x6d, 0xae, 0x5a, 0x10,
+ 0x73, 0xa6, 0x72, 0x76, 0x27, 0x09, 0x7a, 0x10,
+ 0x49, 0xe6, 0x17, 0xd9, 0x1d, 0x36, 0x10, 0x94,
+ 0xfa, 0x68, 0xf0, 0xff, 0x77, 0x98, 0x71, 0x30,
+ 0x30, 0x5b, 0xea, 0xba, 0x2e, 0xda, 0x04, 0xdf,
+ 0x99, 0x7b, 0x71, 0x4d, 0x6c, 0x6f, 0x2c, 0x29,
+ 0xa6, 0xad, 0x5c, 0xb4, 0x02, 0x2b, 0x02, 0x70,
+ 0x9b
+ },
+ .textlen = sizeof (PLAINTEXT_IETF2),
+ .tag = (uint8_t *)&(uint8_t[]) {
+ 0xee, 0xad, 0x9d, 0x67, 0x89, 0x0c, 0xbb, 0x22,
+ 0x39, 0x23, 0x36, 0xfe, 0xa1, 0x85, 0x1f, 0x38
+ },
+ .taglen = 16,
+}, {
+ .name = NULL,
+} };
+
+static int
+test_module_encrypt(const module_test_t *test)
+{
+ crypto_mechanism_t mech;
+ uint8_t outbuf[1024];
+
+ mech.cm_type = crypto_mech2id(SUN_CKM_CHACHA20_POLY1305);
+
+ printf("module test: %s [encrypt]: ", test->name);
+
+ CK_AES_GCM_PARAMS gcmp = {
+ .pIv = (uchar_t *)test->nonce,
+ .ulIvLen = sizeof (test->nonce),
+ .ulIvBits = CRYPTO_BYTES2BITS(sizeof (test->nonce)),
+ .pAAD = (uint8_t *)test->aad,
+ .ulAADLen = test->aadlen,
+ .ulTagBits = CRYPTO_BYTES2BITS(test->taglen),
+ };
+
+ mech.cm_param = (char *)&gcmp;
+ mech.cm_param_len = sizeof (CK_AES_GCM_PARAMS);
+
+ crypto_key_t key = {
+ .ck_length = sizeof (test->key) << 3,
+ .ck_data = (uint8_t *)test->key,
+ };
+
+ crypto_data_t in = {
+ .cd_format = CRYPTO_DATA_RAW,
+ .cd_offset = 0,
+ .cd_length = test->textlen,
+ .cd_raw = {
+ .iov_base = (char *)test->plaintext,
+ .iov_len = test->textlen,
+ },
+ };
+
+ crypto_data_t out = {
+ .cd_format = CRYPTO_DATA_RAW,
+ .cd_offset = 0,
+ .cd_length = test->textlen + test->taglen,
+ .cd_raw = {
+ .iov_base = (char *)outbuf,
+ .iov_len = sizeof (outbuf),
+ },
+ };
+
+ int rv = crypto_encrypt(&mech, &in, &key, NULL, &out);
+ if (rv != CRYPTO_SUCCESS) {
+ printf("FAIL\n");
+ printf(" encrypt rv = 0x%02x\n", rv);
+ return (1);
+ }
+
+ if (memcmp(outbuf, test->ciphertext, test->textlen) != 0) {
+ printf("FAIL\n");
+ printf(" ciphertexts don't match:\n");
+ hexdump("got", outbuf, test->textlen);
+ hexdump("expected", test->ciphertext, test->textlen);
+ return (1);
+ }
+
+ if (memcmp(outbuf + test->textlen, test->tag, test->taglen) != 0) {
+ printf("FAIL\n");
+ printf(" tags don't match:\n");
+ hexdump("got", outbuf + test->textlen, test->taglen);
+ hexdump("expected", test->tag, test->taglen);
+ return (1);
+ }
+
+ printf("SUCCESS\n");
+
+ return (0);
+}
+
+static int
+test_module_decrypt(const module_test_t *test)
+{
+ crypto_mechanism_t mech;
+ uint8_t inbuf[1024], outbuf[1024];
+
+ mech.cm_type = crypto_mech2id(SUN_CKM_CHACHA20_POLY1305);
+
+ printf("module test: %s [decrypt]: ", test->name);
+
+ CK_AES_GCM_PARAMS gcmp = {
+ .pIv = (uchar_t *)test->nonce,
+ .ulIvLen = sizeof (test->nonce),
+ .ulIvBits = CRYPTO_BYTES2BITS(sizeof (test->nonce)),
+ .pAAD = (uint8_t *)test->aad,
+ .ulAADLen = test->aadlen,
+ .ulTagBits = CRYPTO_BYTES2BITS(test->taglen),
+ };
+
+ mech.cm_param = (char *)&gcmp;
+ mech.cm_param_len = sizeof (CK_AES_GCM_PARAMS);
+
+ crypto_key_t key = {
+ .ck_length = sizeof (test->key) << 3,
+ .ck_data = (uint8_t *)test->key,
+ };
+
+ memcpy(inbuf, test->ciphertext, test->textlen);
+ memcpy(inbuf + test->textlen, test->tag, test->taglen);
+ crypto_data_t in = {
+ .cd_format = CRYPTO_DATA_RAW,
+ .cd_offset = 0,
+ .cd_length = test->textlen + test->taglen,
+ .cd_raw = {
+ .iov_base = (char *)inbuf,
+ .iov_len = test->textlen + test->taglen,
+ },
+ };
+
+ crypto_data_t out = {
+ .cd_format = CRYPTO_DATA_RAW,
+ .cd_offset = 0,
+ .cd_length = test->textlen,
+ .cd_raw = {
+ .iov_base = (char *)outbuf,
+ .iov_len = sizeof (outbuf),
+ },
+ };
+
+ int rv = crypto_decrypt(&mech, &in, &key, NULL, &out);
+ if (rv != CRYPTO_SUCCESS) {
+ printf("FAIL\n");
+ printf(" decrypt rv = 0x%02x\n", rv);
+ return (1);
+ }
+
+ if (memcmp(outbuf, test->plaintext, test->textlen) != 0) {
+ printf("FAIL\n");
+ printf(" plaintexts don't match:\n");
+ hexdump("got", outbuf, test->textlen);
+ hexdump("expected", test->plaintext, test->textlen);
+ return (1);
+ }
+ printf("SUCCESS\n");
+
+ return (0);
+}
+
+static int
+test_module(void)
+{
+ int failed = 0;
+
+ icp_init();
+
+ for (int testno = 0; module_tests[testno].name; testno++) {
+ const module_test_t *test = &module_tests[testno];
+ failed |= test_module_encrypt(test);
+ failed |= test_module_decrypt(test);
+ }
+
+ icp_fini();
+
+ return (failed);
+}
+
+int
+main(void)
+{
+ int failed = 0;
+ failed |= test_chacha();
+ failed |= test_poly();
+ failed |= test_module();
+ return (failed);
+}
diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_crypt_combos.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_crypt_combos.ksh
index 758b800c2fe8..b2eb63320a63 100755
--- a/tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_crypt_combos.ksh
+++ b/tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_crypt_combos.ksh
@@ -50,7 +50,8 @@ set -A ENCRYPTION_ALGS \
"encryption=aes-256-ccm" \
"encryption=aes-128-gcm" \
"encryption=aes-192-gcm" \
- "encryption=aes-256-gcm"
+ "encryption=aes-256-gcm" \
+ "encryption=chacha20-poly1305"
set -A ENCRYPTION_PROPS \
"encryption=aes-256-gcm" \
@@ -59,7 +60,8 @@ set -A ENCRYPTION_PROPS \
"encryption=aes-256-ccm" \
"encryption=aes-128-gcm" \
"encryption=aes-192-gcm" \
- "encryption=aes-256-gcm"
+ "encryption=aes-256-gcm" \
+ "encryption=chacha20-poly1305"
set -A KEYFORMATS "keyformat=raw" \
"keyformat=hex" \
diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_chapoly_feature.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_chapoly_feature.ksh
new file mode 100755
index 000000000000..3be9dd4b99ad
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_chapoly_feature.ksh
@@ -0,0 +1,105 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2023, Rob Norris
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# DESCRIPTION:
+# A raw send with a dataset with Chacha20-Poly1305 encryption should only be
+# received when the feature is enabled, otherwise rejected. An AES-GCM dataset
+# however should always be received.
+#
+
+verify_runnable "both"
+
+log_assert "Raw sends with Chacha20-Poly1305 can only be" \
+ "recieved if feature available"
+
+function cleanup
+{
+ poolexists srcpool && destroy_pool srcpool
+ poolexists dstpool && destroy_pool dstpool
+ log_must rm -f $TESTDIR/srcdev $TESTDIR/dstdev
+}
+
+log_onexit cleanup
+
+# create a pool with the the chapoly feature enabled
+truncate -s $MINVDEVSIZE $TESTDIR/srcdev
+log_must zpool create -f -o feature@chacha20_poly1305=enabled \
+ srcpool $TESTDIR/srcdev
+
+# created encrypted filesystems
+echo 'password' | create_dataset srcpool/chapoly \
+ -o encryption=chacha20-poly1305 -o keyformat=passphrase
+echo 'password' | create_dataset srcpool/aesgcm \
+ -o encryption=aes-256-gcm -o keyformat=passphrase
+
+# snapshot everything
+log_must zfs snapshot -r srcpool@snap
+
+
+# create a pool with the chapoly feature enabled
+truncate -s $MINVDEVSIZE $TESTDIR/dstdev
+log_must zpool create -f -o feature@chacha20_poly1305=enabled \
+ dstpool $TESTDIR/dstdev
+
+# send and receive both filesystems
+log_must eval \
+ "zfs send -Rw srcpool/chapoly@snap | zfs receive -u dstpool/chapoly"
+log_must eval \
+ "zfs send -Rw srcpool/aesgcm@snap | zfs receive -u dstpool/aesgcm"
+
+# destroy received datasets
+log_must zfs destroy -r dstpool/chapoly
+log_must zfs destroy -r dstpool/aesgcm
+
+# send and receive entire recursive stream
+log_must eval "zfs send -Rw srcpool@snap | zfs receive -u dstpool/all"
+
+
+# remake the dest pool with the chapoly feature disabled
+destroy_pool dstpool
+log_must zpool create -f -o feature@chacha20_poly1305=disabled \
+ dstpool $TESTDIR/dstdev
+
+# send and receive both filesystems. chapoly shoud fail, but aesgcm should
+# succeed
+log_mustnot eval \
+ "zfs send -Rw srcpool/chapoly@snap | zfs receive -u dstpool/chapoly"
+log_must eval \
+ "zfs send -Rw srcpool/aesgcm@snap | zfs receive -u dstpool/aesgcm"
+
+# destroy received dataset
+log_must zfs destroy -r dstpool/aesgcm
+
+
+# send and receive entire recursive stream, which should fail
+log_mustnot eval "zfs send -Rw srcpool@snap | zfs receive -u dstpool/all"
+
+
+log_pass "Chacha20-Poly1305 datasets can only be recieved" \
+ "if the feature is enabled"
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_create/zpool_create_crypt_combos.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_create/zpool_create_crypt_combos.ksh
index 63391e8adb49..6a2cbbaac7bf 100755
--- a/tests/zfs-tests/tests/functional/cli_root/zpool_create/zpool_create_crypt_combos.ksh
+++ b/tests/zfs-tests/tests/functional/cli_root/zpool_create/zpool_create_crypt_combos.ksh
@@ -46,7 +46,8 @@ set -A ENCRYPTION_ALGS "encryption=on" \
"encryption=aes-256-ccm" \
"encryption=aes-128-gcm" \
"encryption=aes-192-gcm" \
- "encryption=aes-256-gcm"
+ "encryption=aes-256-gcm" \
+ "encryption=chacha20-poly1305"
set -A ENCRYPTION_PROPS "encryption=aes-256-gcm" \
"encryption=aes-128-ccm" \
@@ -54,7 +55,8 @@ set -A ENCRYPTION_PROPS "encryption=aes-256-gcm" \
"encryption=aes-256-ccm" \
"encryption=aes-128-gcm" \
"encryption=aes-192-gcm" \
- "encryption=aes-256-gcm"
+ "encryption=aes-256-gcm" \
+ "encryption=chacha20-poly1305"
set -A KEYFORMATS "keyformat=raw" \
"keyformat=hex" \
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg b/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
index 4248578cde16..ba58aff8e899 100644
--- a/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
+++ b/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
@@ -87,6 +87,7 @@ typeset -a properties=(
"feature@device_rebuild"
"feature@draid"
"feature@redaction_list_spill"
+ "feature@chacha20_poly1305"
)
if is_linux || is_freebsd; then