From f7708bc9e5222ad864d880fca561374f90af3462 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Mon, 18 Sep 2023 21:25:43 +1000 Subject: [PATCH] Chacha20-Poly1305 encryption This commit implements the Chacha20-Poly1305 AEAD from RFC 8439 as a new algorithm option for encrypted datasets. AES (and particularly the default AES-GCM mode used in OpenZFS) is known to be very slow on systems without hardware assistance. There are many such machines out there could make good use of OpenZFS, especially low-cost machines and small boards that would otherwise make very nice storage machines. The Raspberry Pi series of machines are a good example. The best option for these systems is an encryption option that performs well in software. Chacha20-Poly1305 is the current "standard" option for this in many contexts, and is a good choice for OpenZFS. The core Chacha20 and Poly1305 implementations are taken from Loup Valliant's Monocypher. These were chosen because they are compact, easy to read, easy to use and the author has written extensively about its development, all of which give me confidence that there are unlikely to be any surprises. I've added a KCF-style module to the ICP to implement the AEAD. This implements just enough for OpenZFS, and is not suitable as a general-purpose KCF for Illumos (though it could be the starting point for one). For FreeBSD, which does not use the ICP, I've instead hooked it up to FreeBSD's builtin crypto stack. The rest is adding an enabling property value and a feature flag and and hooking it up to all the right touch points, and documentation updates. The existing tests that cycle through the possible encryption options have been extended to add one more. I've added a test to ensure that raw receives of chacha20-poly1305 datasets do the right thing based on the state of the feature flag on the receiving side. There's also a test unit that runs the test vectors in RFC 8439 against Chacha20, Poly1305 and the AEAD in the ICP that combines them. This is most useful as a sanity check during future work to add alternate (accelerated) implementations. Finally, manual interop testing has been done to confirm that pools and streams can be moved between Linux and FreeBSD correctly. Light and uncontrolled performance testing on a Raspberry Pi 4B (Broadcom BCM2711, no hardware AES) writing to a chacha20-poly1305 dataset was ~2.4x faster than aes-256-gcm on the same hardware. On a Fitlet2 (Celeron J3455, AES-NI but no AVX (#10846)) it was ~1.3x faster. Signed-off-by: Rob Norris --- Makefile.am | 1 + include/os/freebsd/zfs/sys/freebsd_crypto.h | 1 + include/sys/crypto/common.h | 1 + include/sys/crypto/icp.h | 3 + include/sys/fs/zfs.h | 1 + include/sys/zio_crypt.h | 5 +- include/zfeature_common.h | 1 + lib/libicp/Makefile.am | 2 + lib/libzfs/libzfs.abi | 3 +- man/man7/zfsprops.7 | 11 +- man/man7/zpool-features.7 | 18 +- module/Kbuild.in | 2 + module/icp/illumos-crypto.c | 2 + module/icp/include/monocypher.h | 95 ++ module/icp/io/chapoly.c | 501 +++++++++++ module/icp/monocypher.c | 377 ++++++++ module/os/freebsd/zfs/crypto_os.c | 19 + module/os/freebsd/zfs/zio_crypt.c | 38 +- module/os/linux/zfs/zio_crypt.c | 29 +- module/zcommon/zfeature_common.c | 13 + module/zcommon/zfs_prop.c | 5 +- module/zfs/dsl_crypt.c | 21 + module/zfs/dsl_pool.c | 6 +- tests/runfiles/common.run | 3 +- tests/runfiles/sanity.run | 3 +- tests/zfs-tests/Makefile.am | 5 + tests/zfs-tests/tests/Makefile.am | 1 + .../tests/functional/chapoly/.gitignore | 1 + .../tests/functional/chapoly/chapoly_test.c | 814 ++++++++++++++++++ .../zfs_create/zfs_create_crypt_combos.ksh | 6 +- .../zfs_receive_chapoly_feature.ksh | 105 +++ .../zpool_create_crypt_combos.ksh | 6 +- .../cli_root/zpool_get/zpool_get.cfg | 1 + 33 files changed, 2064 insertions(+), 36 deletions(-) create mode 100644 module/icp/include/monocypher.h create mode 100644 module/icp/io/chapoly.c create mode 100644 module/icp/monocypher.c create mode 100644 tests/zfs-tests/tests/functional/chapoly/.gitignore create mode 100644 tests/zfs-tests/tests/functional/chapoly/chapoly_test.c create mode 100755 tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_chapoly_feature.ksh 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 21f99bacccf3..721f97ada3c3 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1854,6 +1854,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 2515ba321759..14eef0238275 100644 --- a/include/zfeature_common.h +++ b/include/zfeature_common.h @@ -82,6 +82,7 @@ typedef enum spa_feature { SPA_FEATURE_AVZ_V2, SPA_FEATURE_REDACTION_LIST_SPILL, SPA_FEATURE_RAIDZ_EXPANSION, + 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 2bbaae6345ab..b9c1fadfc258 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -5936,7 +5936,8 @@ - + + diff --git a/man/man7/zfsprops.7 b/man/man7/zfsprops.7 index 9ff0236f4d74..60e242b3cbbc 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 ea3c68dc6083..d22e118d353e 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 . @@ -410,6 +411,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 7e08374fa2b9..6008c92128b3 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -123,9 +123,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..3296d0271ecd --- /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 = vmem_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); + vmem_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 ed8d2407613e..d4210dfb8909 100644 --- a/module/os/freebsd/zfs/crypto_os.c +++ b/module/os/freebsd/zfs/crypto_os.c @@ -302,6 +302,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; @@ -450,6 +461,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; @@ -552,6 +567,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 2b62abcccb78..2b5ed53e7d80 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 21f3740f6fe6..2b15b43f8f86 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 309d9bf14cd4..c94aa94fe6df 100644 --- a/module/zcommon/zfeature_common.c +++ b/module/zcommon/zfeature_common.c @@ -754,6 +754,19 @@ zpool_feature_init(void) "Support for raidz expansion", ZFEATURE_FLAG_MOS, ZFEATURE_TYPE_BOOLEAN, NULL, sfeatures); + { + static const spa_feature_t chapoly_deps[] = { + SPA_FEATURE_EXTENSIBLE_DATASET, + SPA_FEATURE_ENCRYPTION, + SPA_FEATURE_NONE + }; + zfeature_register(SPA_FEATURE_CHACHA20_POLY1305, + "com.despairlabs: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 764993b45e7c..21c034286cdc 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 8e1055d9bcb1..bb4f66ea8d54 100644 --- a/module/zfs/dsl_crypt.c +++ b/module/zfs/dsl_crypt.c @@ -1853,6 +1853,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); @@ -1971,6 +1977,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. @@ -2191,6 +2200,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)); @@ -2310,6 +2324,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 342ec5c15c79..abcb61230c2d 100644 --- a/module/zfs/dsl_pool.c +++ b/module/zfs/dsl_pool.c @@ -530,8 +530,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 912344b4edde..33d779b7e5dc 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -281,7 +281,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 3dd1a6452728..2b10f7a39067 100644 --- a/tests/zfs-tests/Makefile.am +++ b/tests/zfs-tests/Makefile.am @@ -24,6 +24,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 db6b4c0146a7..112b48967d64 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -822,6 +822,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 6ebce9459190..b2077eaa88ab 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