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