diff --git a/IDE/WINVS/wolfprovider/wolfprovider.vcxproj b/IDE/WINVS/wolfprovider/wolfprovider.vcxproj
index dab92529..c34cd440 100644
--- a/IDE/WINVS/wolfprovider/wolfprovider.vcxproj
+++ b/IDE/WINVS/wolfprovider/wolfprovider.vcxproj
@@ -30,6 +30,7 @@
+
diff --git a/README.md b/README.md
index 3db632e3..884c40bb 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ Information on how to configure, build, and test wolfProvider can be found here:
### Symmetric Ciphers
* AES (128, 192, 256-bit keys)
* ECB, CBC, CTR, CFB, CTS
+ * XTS (AES-128-XTS, AES-256-XTS)
* GCM, CCM (AEAD)
* Key Wrap
* 3DES-CBC
diff --git a/include/wolfprovider/alg_funcs.h b/include/wolfprovider/alg_funcs.h
index 771a1a76..68fdf9a8 100644
--- a/include/wolfprovider/alg_funcs.h
+++ b/include/wolfprovider/alg_funcs.h
@@ -125,6 +125,9 @@ typedef void (*DFUNC)(void);
#define WP_NAMES_AES_192_CTS "AES-192-CBC-CTS"
#define WP_NAMES_AES_128_CTS "AES-128-CBC-CTS"
+#define WP_NAMES_AES_256_XTS "AES-256-XTS:1.3.111.2.1619.0.1.2"
+#define WP_NAMES_AES_128_XTS "AES-128-XTS:1.3.111.2.1619.0.1.1"
+
#define WP_NAMES_DES_EDE3_CBC "DES-EDE3-CBC:DES3:1.2.840.113549.3.7"
/* Internal cipher flags. */
@@ -431,6 +434,9 @@ extern const OSSL_DISPATCH wp_aes256cts_functions[];
extern const OSSL_DISPATCH wp_aes192cts_functions[];
extern const OSSL_DISPATCH wp_aes128cts_functions[];
+extern const OSSL_DISPATCH wp_aes256xts_functions[];
+extern const OSSL_DISPATCH wp_aes128xts_functions[];
+
extern const OSSL_DISPATCH wp_des3cbc_functions[];
/* MAC implementations. */
diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h
index cdda021c..68cb0b4c 100644
--- a/include/wolfprovider/settings.h
+++ b/include/wolfprovider/settings.h
@@ -134,6 +134,10 @@
#ifdef WOLFSSL_AES_CFB
#define WP_HAVE_AESCFB
#endif
+/* wc_AesXtsSetKeyNoInit() is available from wolfSSL 5.6.6. */
+#if defined(WOLFSSL_AES_XTS) && (LIBWOLFSSL_VERSION_HEX >= 0x05006006)
+ #define WP_HAVE_AESXTS
+#endif
#ifndef WC_NO_RNG
#define WP_HAVE_RANDOM
diff --git a/src/include.am b/src/include.am
index 4498100a..99e18ccd 100644
--- a/src/include.am
+++ b/src/include.am
@@ -10,6 +10,7 @@ libwolfprov_la_SOURCES += src/wp_aes_block.c
libwolfprov_la_SOURCES += src/wp_aes_stream.c
libwolfprov_la_SOURCES += src/wp_aes_aead.c
libwolfprov_la_SOURCES += src/wp_aes_wrap.c
+libwolfprov_la_SOURCES += src/wp_aes_xts.c
libwolfprov_la_SOURCES += src/wp_des.c
libwolfprov_la_SOURCES += src/wp_hmac.c
libwolfprov_la_SOURCES += src/wp_cmac.c
diff --git a/src/wp_aes_xts.c b/src/wp_aes_xts.c
new file mode 100644
index 00000000..72892daa
--- /dev/null
+++ b/src/wp_aes_xts.c
@@ -0,0 +1,617 @@
+/* wp_aes_xts.c
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfProvider.
+ *
+ * wolfProvider is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfProvider is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfProvider. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#ifdef WP_HAVE_AESXTS
+
+/**
+ * Maximum data unit size: 2^20 blocks (IEEE Std 1619-2018, NIST SP 800-38E).
+ */
+#define WP_AES_XTS_MAX_BYTES ((size_t)AES_BLOCK_SIZE << 20)
+
+/**
+ * Data structure for AES-XTS ciphers.
+ *
+ * Matches OpenSSL's AES-XTS: the IV is the 16-byte tweak, and each update is
+ * one complete data unit processed with that tweak.
+ */
+typedef struct wp_AesXtsCtx {
+ /** wolfSSL AES-XTS object: data key and tweak key. */
+ XtsAes xts;
+
+ /** Length of key in bytes: both halves. */
+ size_t keyLen;
+
+ /** Operation being performed is encryption. */
+ unsigned int enc:1;
+ /** Key has been set. */
+ unsigned int keySet:1;
+ /** Key schedule was set up for encryption. */
+ unsigned int keyEnc:1;
+ /** Tweak has been set. */
+ unsigned int ivSet:1;
+
+ /** Tweak. Not changed by an update. */
+ unsigned char iv[AES_BLOCK_SIZE];
+} wp_AesXtsCtx;
+
+
+/* Prototype for initialization to call. */
+static int wp_aes_xts_set_ctx_params(wp_AesXtsCtx *ctx,
+ const OSSL_PARAM params[]);
+
+
+/**
+ * Free the AES-XTS context object.
+ *
+ * @param [in, out] ctx AES-XTS context object.
+ */
+static void wp_aes_xts_freectx(wp_AesXtsCtx *ctx)
+{
+ wc_AesXtsFree(&ctx->xts);
+ OPENSSL_clear_free(ctx, sizeof(*ctx));
+}
+
+/**
+ * Duplicate the AES-XTS context object.
+ *
+ * @param [in] src AES-XTS context object to copy.
+ * @return NULL on failure.
+ * @return AES-XTS context object.
+ */
+static void *wp_aes_xts_dupctx(wp_AesXtsCtx *src)
+{
+ wp_AesXtsCtx *dst = NULL;
+
+ if (wolfssl_prov_is_running()) {
+ dst = OPENSSL_malloc(sizeof(*dst));
+ }
+ if (dst != NULL) {
+ /* XtsAes is copied by value, as Aes is for the other AES ciphers. */
+ XMEMCPY(dst, src, sizeof(*src));
+ }
+
+ return dst;
+}
+
+/**
+ * Returns the parameters that can be retrieved.
+ *
+ * @param [in] provCtx wolfProvider context object. Unused.
+ * @return Array of parameters.
+ */
+static const OSSL_PARAM *wp_aes_xts_gettable_params(WOLFPROV_CTX *provCtx)
+{
+ /**
+ * Parameters able to be retrieved for an AES-XTS cipher.
+ */
+ static const OSSL_PARAM wp_aes_xts_supported_gettable_params[] = {
+ OSSL_PARAM_uint(OSSL_CIPHER_PARAM_MODE, NULL),
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_KEYLEN, NULL),
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_IVLEN, NULL),
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_BLOCK_SIZE, NULL),
+ OSSL_PARAM_int(OSSL_CIPHER_PARAM_CUSTOM_IV, NULL),
+ OSSL_PARAM_int(OSSL_CIPHER_PARAM_HAS_RAND_KEY, NULL),
+ OSSL_PARAM_END
+ };
+ (void)provCtx;
+ return wp_aes_xts_supported_gettable_params;
+}
+
+/**
+ * Get the values of an AES-XTS cipher for the parameters.
+ *
+ * @param [in, out] params Array of parameters to retrieve.
+ * @param [in] kBits Number of bits in the key: both halves.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_aes_xts_get_params(OSSL_PARAM params[], size_t kBits)
+{
+ int ok = 1;
+ OSSL_PARAM *p;
+
+ WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aes_xts_get_params");
+
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_MODE);
+ if ((p != NULL) && (!OSSL_PARAM_set_uint(p, EVP_CIPH_XTS_MODE))) {
+ ok = 0;
+ }
+ if (ok) {
+ /* The IV is the tweak, as in OpenSSL. */
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_CUSTOM_IV);
+ if ((p != NULL) && (!OSSL_PARAM_set_int(p, 1))) {
+ ok = 0;
+ }
+ }
+ if (ok) {
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_HAS_RAND_KEY);
+ if ((p != NULL) && (!OSSL_PARAM_set_int(p, 0))) {
+ ok = 0;
+ }
+ }
+ if (ok) {
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_KEYLEN);
+ if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, kBits / 8))) {
+ ok = 0;
+ }
+ }
+ if (ok) {
+ /* Any length of at least one block is processed in one update. */
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_BLOCK_SIZE);
+ if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, 1))) {
+ ok = 0;
+ }
+ }
+ if (ok) {
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN);
+ if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, AES_BLOCK_SIZE))) {
+ ok = 0;
+ }
+ }
+
+ WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
+ return ok;
+}
+
+/**
+ * Returns the parameters of a cipher context that can be retrieved.
+ *
+ * @param [in] ctx AES-XTS context object. Unused.
+ * @param [in] provCtx wolfProvider context object. Unused.
+ * @return Array of parameters.
+ */
+static const OSSL_PARAM* wp_aes_xts_gettable_ctx_params(wp_AesXtsCtx* ctx,
+ WOLFPROV_CTX* provCtx)
+{
+ /**
+ * Parameters able to be retrieved for a cipher context.
+ */
+ static const OSSL_PARAM wp_aes_xts_supported_gettable_ctx_params[] = {
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_KEYLEN, NULL),
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_IVLEN, NULL),
+ OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_IV, NULL, 0),
+ OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_UPDATED_IV, NULL, 0),
+ OSSL_PARAM_END
+ };
+ (void)ctx;
+ (void)provCtx;
+ return wp_aes_xts_supported_gettable_ctx_params;
+}
+
+/**
+ * Returns the parameters of a cipher context that can be set.
+ *
+ * @param [in] ctx AES-XTS context object. Unused.
+ * @param [in] provCtx wolfProvider context object. Unused.
+ * @return Array of parameters.
+ */
+static const OSSL_PARAM* wp_aes_xts_settable_ctx_params(wp_AesXtsCtx* ctx,
+ WOLFPROV_CTX *provCtx)
+{
+ /*
+ * Parameters able to be set into a cipher context.
+ */
+ static const OSSL_PARAM wp_aes_xts_supported_settable_ctx_params[] = {
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_KEYLEN, NULL),
+ OSSL_PARAM_END
+ };
+ (void)ctx;
+ (void)provCtx;
+ return wp_aes_xts_supported_settable_ctx_params;
+}
+
+/**
+ * Initialization of an AES-XTS cipher.
+ *
+ * Internal. Handles both encryption and decryption.
+ *
+ * @param [in, out] ctx AES-XTS context object.
+ * @param [in] key Data key followed by tweak key. May be NULL.
+ * @param [in] keyLen Length of key in bytes.
+ * @param [in] iv Tweak. May be NULL.
+ * @param [in] ivLen Length of tweak in bytes.
+ * @param [in] params Parameters to set against AES-XTS context object.
+ * @param [in] enc Initializing for encryption.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_aes_xts_init(wp_AesXtsCtx *ctx, const unsigned char *key,
+ size_t keyLen, const unsigned char *iv, size_t ivLen,
+ const OSSL_PARAM params[], int enc)
+{
+ int ok = 1;
+
+ WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aes_xts_init");
+
+ if (!wolfssl_prov_is_running()) {
+ ok = 0;
+ }
+ if (ok) {
+ ctx->enc = enc;
+ }
+
+ if (ok && (iv != NULL)) {
+ if (ivLen != AES_BLOCK_SIZE) {
+ ok = 0;
+ }
+ if (ok) {
+ XMEMCPY(ctx->iv, iv, ivLen);
+ ctx->ivSet = 1;
+ }
+ }
+
+ if (ok && (key != NULL)) {
+ int rc;
+
+ if (keyLen != ctx->keyLen) {
+ ok = 0;
+ }
+ /* The two halves must differ (IEEE Std 1619-2018, SP 800-38E).
+ * OpenSSL's FIPS provider rejects equal halves; so do we, in both
+ * directions and whatever the wolfSSL build. */
+ if (ok && (CRYPTO_memcmp(key, key + keyLen / 2, keyLen / 2) == 0)) {
+ WOLFPROV_MSG_DEBUG(WP_LOG_COMP_AES,
+ "AES-XTS data and tweak keys are equal");
+ ok = 0;
+ }
+ if (ok) {
+ WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES);
+ rc = wc_AesXtsSetKeyNoInit(&ctx->xts, key, (word32)keyLen,
+ enc ? AES_ENCRYPTION : AES_DECRYPTION);
+ if (rc != 0) {
+ WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG,
+ "wc_AesXtsSetKeyNoInit", rc);
+ ok = 0;
+ }
+ }
+ if (ok) {
+ ctx->keySet = 1;
+ ctx->keyEnc = enc;
+ }
+ else {
+ /* A failed key setup leaves no usable key. */
+ ctx->keySet = 0;
+ }
+ }
+ else if (ok && ctx->keySet && (ctx->keyEnc != (unsigned int)enc)) {
+ /* The key schedule is for one direction only: changing direction
+ * needs the key again. */
+ ok = 0;
+ }
+
+ if (ok) {
+ ok = wp_aes_xts_set_ctx_params(ctx, params);
+ }
+
+ WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
+ return ok;
+}
+
+/**
+ * Initialization of an AES-XTS encryption.
+ *
+ * @param [in, out] ctx AES-XTS context object.
+ * @param [in] key Data key followed by tweak key. May be NULL.
+ * @param [in] keyLen Length of key in bytes.
+ * @param [in] iv Tweak. May be NULL.
+ * @param [in] ivLen Length of tweak in bytes.
+ * @param [in] params Parameters to set against AES-XTS context object.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_aes_xts_einit(wp_AesXtsCtx *ctx, const unsigned char *key,
+ size_t keyLen, const unsigned char *iv, size_t ivLen,
+ const OSSL_PARAM params[])
+{
+ return wp_aes_xts_init(ctx, key, keyLen, iv, ivLen, params, 1);
+}
+
+/**
+ * Initialization of an AES-XTS decryption.
+ *
+ * @param [in, out] ctx AES-XTS context object.
+ * @param [in] key Data key followed by tweak key. May be NULL.
+ * @param [in] keyLen Length of key in bytes.
+ * @param [in] iv Tweak. May be NULL.
+ * @param [in] ivLen Length of tweak in bytes.
+ * @param [in] params Parameters to set against AES-XTS context object.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_aes_xts_dinit(wp_AesXtsCtx *ctx, const unsigned char *key,
+ size_t keyLen, const unsigned char *iv, size_t ivLen,
+ const OSSL_PARAM params[])
+{
+ return wp_aes_xts_init(ctx, key, keyLen, iv, ivLen, params, 0);
+}
+
+/**
+ * Encrypt/decrypt one data unit.
+ *
+ * The whole input is one data unit: at least one block, a partial last block
+ * handled with ciphertext stealing, and at most 2^20 blocks. Every call uses
+ * the tweak set at initialization.
+ *
+ * @param [in] ctx AES-XTS context object.
+ * @param [out] out Buffer to hold encrypted/decrypted result.
+ * @param [out] outLen Length of encrypted/decrypted data in bytes.
+ * @param [in] outSize Size of output buffer in bytes.
+ * @param [in] in Data to encrypt/decrypt.
+ * @param [in] inLen Length of data in bytes.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_aes_xts_cipher(wp_AesXtsCtx *ctx, unsigned char *out,
+ size_t *outLen, size_t outSize, const unsigned char *in, size_t inLen)
+{
+ int ok = 1;
+
+ WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aes_xts_cipher");
+
+ if (!wolfssl_prov_is_running()) {
+ ok = 0;
+ }
+ /* The key schedule must be for the direction in use: a failed re-init
+ * may have changed the direction. */
+ if (ok && ((!ctx->keySet) || (!ctx->ivSet) ||
+ (ctx->keyEnc != ctx->enc))) {
+ ok = 0;
+ }
+ if (ok && ((in == NULL) || (out == NULL))) {
+ ok = 0;
+ }
+ if (ok && ((inLen < AES_BLOCK_SIZE) || (inLen > WP_AES_XTS_MAX_BYTES))) {
+ ok = 0;
+ }
+ if (ok && (outSize < inLen)) {
+ ok = 0;
+ }
+
+ if (ok) {
+ int rc;
+
+ if (ctx->enc) {
+ rc = wc_AesXtsEncrypt(&ctx->xts, out, in, (word32)inLen, ctx->iv,
+ AES_BLOCK_SIZE);
+ if (rc != 0) {
+ WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG,
+ "wc_AesXtsEncrypt", rc);
+ ok = 0;
+ }
+ }
+ else {
+ rc = wc_AesXtsDecrypt(&ctx->xts, out, in, (word32)inLen, ctx->iv,
+ AES_BLOCK_SIZE);
+ if (rc != 0) {
+ WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG,
+ "wc_AesXtsDecrypt", rc);
+ ok = 0;
+ }
+ }
+ }
+
+ if (ok) {
+ *outLen = inLen;
+ }
+
+ WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
+ return ok;
+}
+
+/**
+ * Finalize AES-XTS encryption/decryption. Nothing to do: each update is a
+ * complete data unit.
+ *
+ * @param [in] ctx AES-XTS context object.
+ * @param [out] out Buffer to hold encrypted/decrypted data.
+ * @param [out] outLen Length of data encrypted/decrypted in bytes.
+ * @param [in] outSize Size of buffer.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_aes_xts_final(wp_AesXtsCtx* ctx, unsigned char *out,
+ size_t *outLen, size_t outSize)
+{
+ int ok = 1;
+
+ WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aes_xts_final");
+
+ (void)ctx;
+ (void)out;
+ (void)outSize;
+
+ if (!wolfssl_prov_is_running()) {
+ ok = 0;
+ }
+ if (ok) {
+ *outLen = 0;
+ }
+
+ WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
+ return ok;
+}
+
+/**
+ * Put values from the AES-XTS context object into parameters objects.
+ *
+ * @param [in] ctx AES-XTS context object.
+ * @param [in, out] params Array of parameters objects.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_aes_xts_get_ctx_params(wp_AesXtsCtx* ctx, OSSL_PARAM params[])
+{
+ int ok = 1;
+ OSSL_PARAM* p;
+
+ WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aes_xts_get_ctx_params");
+
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN);
+ if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, AES_BLOCK_SIZE))) {
+ ok = 0;
+ }
+ if (ok) {
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_KEYLEN);
+ if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, ctx->keyLen))) {
+ ok = 0;
+ }
+ }
+ if (ok) {
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IV);
+ if ((p != NULL) &&
+ (!OSSL_PARAM_set_octet_ptr(p, &ctx->iv, AES_BLOCK_SIZE)) &&
+ (!OSSL_PARAM_set_octet_string(p, &ctx->iv, AES_BLOCK_SIZE))) {
+ ok = 0;
+ }
+ }
+ if (ok) {
+ /* The tweak is not advanced by an update. */
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_UPDATED_IV);
+ if ((p != NULL) &&
+ (!OSSL_PARAM_set_octet_ptr(p, &ctx->iv, AES_BLOCK_SIZE)) &&
+ (!OSSL_PARAM_set_octet_string(p, &ctx->iv, AES_BLOCK_SIZE))) {
+ ok = 0;
+ }
+ }
+
+ WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
+ return ok;
+}
+
+/**
+ * Sets the parameters to use into AES-XTS context object.
+ *
+ * @param [in, out] ctx AES-XTS context object.
+ * @param [in] params Array of parameter objects.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_aes_xts_set_ctx_params(wp_AesXtsCtx *ctx,
+ const OSSL_PARAM params[])
+{
+ int ok = 1;
+
+ WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aes_xts_set_ctx_params");
+
+ if (params != NULL) {
+ size_t keyLen = ctx->keyLen;
+
+ if (!wp_params_get_size_t(params, OSSL_CIPHER_PARAM_KEYLEN, &keyLen)) {
+ ok = 0;
+ }
+ /* The key length is fixed by the algorithm. */
+ if (ok && (keyLen != ctx->keyLen)) {
+ ok = 0;
+ }
+ }
+
+ WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
+ return ok;
+}
+
+/** Implement the get params API for an AES-XTS cipher. */
+#define IMPLEMENT_AES_XTS_GET_PARAMS(kBits) \
+/** \
+ * Get the values of the AES-XTS cipher for the parameters. \
+ * \
+ * @param [in, out] params Array of parameters to retrieve. \
+ * @return 1 on success. \
+ * @return 0 on failure. \
+ */ \
+static int wp_aes_##kBits##_xts_get_params(OSSL_PARAM params[]) \
+{ \
+ return wp_aes_xts_get_params(params, 2 * (kBits)); \
+}
+
+/** Implement the new context API for an AES-XTS cipher. */
+#define IMPLEMENT_AES_XTS_NEWCTX(kBits) \
+/** \
+ * Create a new AES-XTS context object. \
+ * \
+ * @param [in] provCtx Provider context object. \
+ * @return NULL on failure. \
+ * @return AES-XTS context object on success. \
+ */ \
+static wp_AesXtsCtx* wp_aes_xts_##kBits##_newctx(WOLFPROV_CTX *provCtx) \
+{ \
+ wp_AesXtsCtx *ctx = NULL; \
+ (void)provCtx; \
+ if (wolfssl_prov_is_running()) { \
+ ctx = OPENSSL_zalloc(sizeof(*ctx)); \
+ } \
+ if ((ctx != NULL) && \
+ (wc_AesXtsInit(&ctx->xts, NULL, INVALID_DEVID) != 0)) { \
+ OPENSSL_free(ctx); \
+ ctx = NULL; \
+ } \
+ if (ctx != NULL) { \
+ ctx->keyLen = 2 * (kBits) / 8; \
+ } \
+ return ctx; \
+}
+
+/** Implement the dispatch table for an AES-XTS cipher. */
+#define IMPLEMENT_AES_XTS_DISPATCH(kBits) \
+const OSSL_DISPATCH wp_aes##kBits##xts_functions[] = { \
+ { OSSL_FUNC_CIPHER_NEWCTX, (DFUNC)wp_aes_xts_##kBits##_newctx }, \
+ { OSSL_FUNC_CIPHER_FREECTX, (DFUNC)wp_aes_xts_freectx }, \
+ { OSSL_FUNC_CIPHER_DUPCTX, (DFUNC)wp_aes_xts_dupctx }, \
+ { OSSL_FUNC_CIPHER_ENCRYPT_INIT, (DFUNC)wp_aes_xts_einit }, \
+ { OSSL_FUNC_CIPHER_DECRYPT_INIT, (DFUNC)wp_aes_xts_dinit }, \
+ { OSSL_FUNC_CIPHER_UPDATE, (DFUNC)wp_aes_xts_cipher }, \
+ { OSSL_FUNC_CIPHER_FINAL, (DFUNC)wp_aes_xts_final }, \
+ { OSSL_FUNC_CIPHER_CIPHER, (DFUNC)wp_aes_xts_cipher }, \
+ { OSSL_FUNC_CIPHER_GET_PARAMS, \
+ (DFUNC)wp_aes_##kBits##_xts_get_params }, \
+ { OSSL_FUNC_CIPHER_GET_CTX_PARAMS, (DFUNC)wp_aes_xts_get_ctx_params }, \
+ { OSSL_FUNC_CIPHER_SET_CTX_PARAMS, (DFUNC)wp_aes_xts_set_ctx_params }, \
+ { OSSL_FUNC_CIPHER_GETTABLE_PARAMS, (DFUNC)wp_aes_xts_gettable_params }, \
+ { OSSL_FUNC_CIPHER_GETTABLE_CTX_PARAMS, \
+ (DFUNC)wp_aes_xts_gettable_ctx_params }, \
+ { OSSL_FUNC_CIPHER_SETTABLE_CTX_PARAMS, \
+ (DFUNC)wp_aes_xts_settable_ctx_params }, \
+ { 0, NULL } \
+};
+
+/** Implements the functions calling base functions for an AES-XTS cipher. */
+#define IMPLEMENT_AES_XTS(kBits) \
+IMPLEMENT_AES_XTS_GET_PARAMS(kBits) \
+IMPLEMENT_AES_XTS_NEWCTX(kBits) \
+IMPLEMENT_AES_XTS_DISPATCH(kBits)
+
+/*
+ * AES-XTS. kBits is the size of each of the two AES keys, as in the name.
+ */
+
+/** wp_aes256xts_functions */
+IMPLEMENT_AES_XTS(256)
+/** wp_aes128xts_functions */
+IMPLEMENT_AES_XTS(128)
+
+#endif /* WP_HAVE_AESXTS */
diff --git a/src/wp_wolfprov.c b/src/wp_wolfprov.c
index 69446838..2c0cd837 100644
--- a/src/wp_wolfprov.c
+++ b/src/wp_wolfprov.c
@@ -397,9 +397,12 @@ static int wolfprov_get_params(void* provCtx, OSSL_PARAM params[])
#ifdef HAVE_FIPS
/* Properties of wolfSSL provider: name and FIPS wolfSSL. */
#define WOLFPROV_PROPERTIES "provider=wolfprov,fips=yes"
+/* Algorithms outside the FIPS claim: a fips=yes query does not get them. */
+#define WOLFPROV_PROPERTIES_NON_FIPS "provider=wolfprov,fips=no"
#else
/* Properties of wolfSSL provider: name only. */
#define WOLFPROV_PROPERTIES "provider=wolfprov"
+#define WOLFPROV_PROPERTIES_NON_FIPS WOLFPROV_PROPERTIES
#endif
/* List of digest algorithm implementations available in wolfSSL provider. */
@@ -541,6 +544,16 @@ static const OSSL_ALGORITHM wolfprov_ciphers[] = {
"" },
#endif
+#ifdef WP_HAVE_AESXTS
+ /* AES-XTS */
+ { WP_NAMES_AES_256_XTS, WOLFPROV_PROPERTIES_NON_FIPS,
+ wp_aes256xts_functions,
+ "" },
+ { WP_NAMES_AES_128_XTS, WOLFPROV_PROPERTIES_NON_FIPS,
+ wp_aes128xts_functions,
+ "" },
+#endif
+
#ifdef HAVE_AES_KEYWRAP
/* AES Kwy Wrap - unpadded */
{ WP_NAMES_AES_256_WRAP, WOLFPROV_PROPERTIES, wp_aes256wrap_functions,
diff --git a/test/test_cipher.c b/test/test_cipher.c
index f0172569..ea014ba0 100644
--- a/test/test_cipher.c
+++ b/test/test_cipher.c
@@ -2372,3 +2372,300 @@ int test_des3_cbc_large_update(void *data)
return err;
}
#endif /* WP_HAVE_DES3CBC */
+
+#ifdef WP_HAVE_AESXTS
+
+/* Encrypt or decrypt one data unit: a single update, and a final that outputs
+ * nothing. */
+static int test_cipher_xts_crypt(const EVP_CIPHER *cipher, int enc,
+ const unsigned char *key, const unsigned char *iv, const unsigned char *in,
+ int len, unsigned char *out)
+{
+ int err;
+ EVP_CIPHER_CTX *ctx;
+ int outLen = 0;
+ int fLen = 0;
+
+ err = (ctx = EVP_CIPHER_CTX_new()) == NULL;
+ if (err == 0) {
+ err = EVP_CipherInit_ex2(ctx, cipher, key, iv, enc, NULL) != 1;
+ }
+ if (err == 0) {
+ err = EVP_CipherUpdate(ctx, out, &outLen, in, len) != 1;
+ }
+ if (err == 0) {
+ err = EVP_CipherFinal_ex(ctx, out + outLen, &fLen) != 1;
+ }
+ if (err == 0) {
+ err = (outLen != len) || (fLen != 0);
+ }
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return err;
+}
+
+static int test_cipher_xts_kat(const EVP_CIPHER *cipher, int keyLen)
+{
+ int err = 0;
+ const struct {
+ const char *key;
+ const char *iv;
+ const char *pt;
+ const char *ct;
+ int keyLen;
+ int len;
+ } vects[] = {
+ /* IEEE Std 1619-2007 vector 2 */
+ {
+ "\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11"
+ "\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22",
+ "\x33\x33\x33\x33\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ "\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44"
+ "\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44",
+ "\xc4\x54\x18\x5e\x6a\x16\x93\x6e\x39\x33\x40\x38\xac\xef\x83\x8b"
+ "\xfb\x18\x6f\xff\x74\x80\xad\xc4\x28\x93\x82\xec\xd6\xd3\x94\xf0",
+ 32, 32
+ },
+ /* IEEE Std 1619-2007 vector 15: partial last block */
+ {
+ "\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf0"
+ "\xbf\xbe\xbd\xbc\xbb\xba\xb9\xb8\xb7\xb6\xb5\xb4\xb3\xb2\xb1\xb0",
+ "\x9a\x78\x56\x34\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+ "\x10",
+ "\x6c\x16\x25\xdb\x46\x71\x52\x2d\x3d\x75\x99\x60\x1d\xe7\xca\x09"
+ "\xed",
+ 32, 17
+ },
+ /* NIST CAVP XTSGenAES256 (hex tweak) ENCRYPT COUNT 1 */
+ {
+ "\x1e\xa6\x61\xc5\x8d\x94\x3a\x0e\x48\x01\xe4\x2f\x4b\x09\x47\x14"
+ "\x9e\x7f\x9f\x8e\x3e\x68\xd0\xc7\x50\x52\x10\xbd\x31\x1a\x0e\x7c"
+ "\xd6\xe1\x3f\xfd\xf2\x41\x8d\x8d\x19\x11\xc0\x04\xcd\xa5\x8d\xa3"
+ "\xd6\x19\xb7\xe2\xb9\x14\x1e\x58\x31\x8e\xea\x39\x2c\xf4\x1b\x08",
+ "\xad\xf8\xd9\x26\x27\x46\x4a\xd2\xf0\x42\x8e\x84\xa9\xf8\x75\x64",
+ "\x2e\xed\xea\x52\xcd\x82\x15\xe1\xac\xc6\x47\xe8\x10\xbb\xc3\x64"
+ "\x2e\x87\x28\x7f\x8d\x2e\x57\xe3\x6c\x0a\x24\xfb\xc1\x2a\x20\x2e",
+ "\xcb\xaa\xd0\xe2\xf6\xce\xa3\xf5\x0b\x37\xf9\x34\xd4\x6a\x9b\x13"
+ "\x0b\x9d\x54\xf0\x7e\x34\xf3\x6a\xf7\x93\xe8\x6f\x73\xc6\xd7\xdb",
+ 64, 32
+ },
+ /* NIST CAVP XTSGenAES128 (hex tweak) ENCRYPT COUNT 1 */
+ {
+ "\xa1\xb9\x0c\xba\x3f\x06\xac\x35\x3b\x2c\x34\x38\x76\x08\x17\x62"
+ "\x09\x09\x23\x02\x6e\x91\x77\x18\x15\xf2\x9d\xab\x01\x93\x2f\x2f",
+ "\x4f\xae\xf7\x11\x7c\xda\x59\xc6\x6e\x4b\x92\x01\x3e\x76\x8a\xd5",
+ "\xeb\xab\xce\x95\xb1\x4d\x3c\x8d\x6f\xb3\x50\x39\x07\x90\x31\x1c",
+ "\x77\x8a\xe8\xb4\x3c\xb9\x8d\x5a\x82\x50\x81\xd5\xbe\x47\x1c\x63",
+ 32, 16
+ },
+ /* NIST CAVP XTSGenAES128 (hex tweak) ENCRYPT, DataUnitLen 200: partial
+ * last block */
+ {
+ "\x39\x4c\x97\x88\x1a\xbd\x98\x9d\x29\xc7\x03\xe4\x8a\x72\xb3\x97"
+ "\xa7\xac\xf5\x1b\x59\x64\x9e\xee\xa9\xb3\x32\x74\xd8\x54\x1d\xf4",
+ "\x4b\x15\xc6\x84\xa1\x52\xd4\x85\xfe\x99\x37\xd3\x9b\x16\x8c\x29",
+ "\x2f\x3b\x9d\xcf\xba\xe7\x29\x58\x3b\x1d\x1f\xfd\xd1\x6b\xb6\xfe"
+ "\x27\x57\x32\x94\x35\x66\x2a\x78\xf0",
+ "\xf3\x47\x38\x02\xe3\x8a\x3f\xfe\xf4\xd4\xfb\x8e\x6a\xa2\x66\xeb"
+ "\xde\x55\x3a\x64\x52\x8a\x06\x46\x3e",
+ 32, 25
+ },
+ /* NIST CAVP XTSGenAES128 (hex tweak) DECRYPT COUNT 1 */
+ {
+ "\xc4\x3c\xd0\xb2\x37\x98\xee\x3d\xb0\x05\x3d\x1e\x4d\x18\x5e\x96"
+ "\x5d\x67\xfd\xda\x8c\x53\x25\xcc\x70\x9f\xc3\x97\x3f\x05\xcd\x17",
+ "\x79\x00\x43\x2e\x60\x21\xbc\x0e\x62\x7c\x7b\x96\xca\x08\xb4\xd0",
+ "\x07\xf2\xc2\xd4\xe6\xdb\x6e\x12\x00\xbc\x16\x5d\x15\x4e\x06\x98",
+ "\x34\x54\xf7\xd3\x4c\x0c\xaf\xfa\x12\xe9\xd2\x85\x0b\x03\x7f\xff",
+ 32, 16
+ },
+ /* NIST CAVP XTSGenAES256 (hex tweak) DECRYPT COUNT 1 */
+ {
+ "\xd6\xc4\xcf\x73\xc6\x39\xe0\x25\x65\x4d\xd3\x23\x2f\xe3\xaa\x71"
+ "\x38\xf2\x1b\xc8\x92\x22\x71\xb4\xa6\xc0\xaf\x99\x91\x00\xb6\xb5"
+ "\xe3\x80\xec\x7e\xc8\xda\x88\xe6\x81\x6c\xd7\xf4\xf2\x6e\x7a\xc0"
+ "\xf8\x6e\x4c\xaa\xc3\xbe\x55\x23\x4e\xbc\xd4\x34\x7c\xda\x2f\xa5",
+ "\x04\x1f\x41\xfa\x30\xb7\x88\x98\x04\x0b\x5e\x0e\xcb\xa2\x7d\x2b",
+ "\xb8\xf3\x3d\xd3\x8c\x13\x8d\xac\xa2\x27\x72\x8e\x19\xb6\x2c\x4a"
+ "\xd5\xad\x51\x6e\xe2\xc3\xaf\x34\x31\x09\x7f\xf2\x81\x95\x6d\x7d",
+ "\xd0\x83\xf3\x7a\x61\x60\xac\x25\xc3\x22\x98\x00\xae\x07\x21\xd9"
+ "\x4b\xf6\xa9\xff\x2f\x73\xa4\x18\x54\x4e\x6c\x78\x7c\xbc\xd3\x4a",
+ 64, 32
+ },
+ };
+ unsigned char out[32];
+ size_t i;
+
+ PRINT_MSG("Running XTS Known Answer Tests");
+
+ for (i = 0; (err == 0) && (i < sizeof(vects) / sizeof(vects[0])); i++) {
+ if (vects[i].keyLen != keyLen) {
+ continue;
+ }
+ err = test_cipher_xts_crypt(cipher, 1, (const unsigned char *)vects[i].key,
+ (const unsigned char *)vects[i].iv, (const unsigned char *)vects[i].pt,
+ vects[i].len, out);
+ if ((err == 0) && (memcmp(out, vects[i].ct, vects[i].len) != 0)) {
+ PRINT_MSG("KAT Encryption output mismatch for vector %zu", i + 1);
+ err = 1;
+ }
+ if (err == 0) {
+ err = test_cipher_xts_crypt(cipher, 0,
+ (const unsigned char *)vects[i].key,
+ (const unsigned char *)vects[i].iv,
+ (const unsigned char *)vects[i].ct, vects[i].len, out);
+ }
+ if ((err == 0) && (memcmp(out, vects[i].pt, vects[i].len) != 0)) {
+ PRINT_MSG("KAT Decryption output mismatch for vector %zu", i + 1);
+ err = 1;
+ }
+ }
+
+ return err;
+}
+
+static int test_cipher_xts(void *data, const char *cipher, int keyLen)
+{
+ int err = 0;
+ /* One block, partial last blocks and several blocks. */
+ static const int lens[] = { 16, 17, 31, 32, 33, 47, 100, 512, 1023 };
+ unsigned char key[64];
+ unsigned char iv[16];
+ unsigned char msg[1023];
+ unsigned char oEnc[sizeof(msg)];
+ unsigned char wEnc[sizeof(msg)];
+ unsigned char dec[sizeof(msg)];
+ EVP_CIPHER_CTX *ctx = NULL;
+ EVP_CIPHER* ocipher;
+ EVP_CIPHER* wcipher;
+ int outLen;
+ size_t i;
+
+ (void)data;
+
+ ocipher = EVP_CIPHER_fetch(osslLibCtx, cipher, "");
+ wcipher = EVP_CIPHER_fetch(wpLibCtx, cipher, "");
+ if ((ocipher == NULL) || (wcipher == NULL)) {
+ err = 1;
+ }
+
+ if (err == 0) {
+ err = test_cipher_xts_kat(wcipher, keyLen);
+ }
+
+ if (err == 0) {
+ err = RAND_bytes(key, keyLen) != 1;
+ }
+ if (err == 0) {
+ /* Halves must differ. */
+ key[0] = key[keyLen / 2] ^ 0x01;
+ err = RAND_bytes(iv, sizeof(iv)) != 1;
+ }
+ if (err == 0) {
+ err = RAND_bytes(msg, sizeof(msg)) != 1;
+ }
+
+ /* Interop with OpenSSL: same ciphertext, and each decrypts the other's. */
+ for (i = 0; (err == 0) && (i < sizeof(lens) / sizeof(lens[0])); i++) {
+ PRINT_MSG("XTS interop, %d bytes", lens[i]);
+ err = test_cipher_xts_crypt(ocipher, 1, key, iv, msg, lens[i], oEnc);
+ if (err == 0) {
+ err = test_cipher_xts_crypt(wcipher, 1, key, iv, msg, lens[i], wEnc);
+ }
+ if ((err == 0) && (memcmp(oEnc, wEnc, lens[i]) != 0)) {
+ PRINT_MSG("Ciphertext differs from OpenSSL");
+ err = 1;
+ }
+ if (err == 0) {
+ err = test_cipher_xts_crypt(wcipher, 0, key, iv, oEnc, lens[i], dec);
+ }
+ if ((err == 0) && (memcmp(dec, msg, lens[i]) != 0)) {
+ PRINT_MSG("wolfProvider decryption of OpenSSL ciphertext failed");
+ err = 1;
+ }
+ if (err == 0) {
+ err = test_cipher_xts_crypt(ocipher, 0, key, iv, wEnc, lens[i], dec);
+ }
+ if ((err == 0) && (memcmp(dec, msg, lens[i]) != 0)) {
+ PRINT_MSG("OpenSSL decryption of wolfProvider ciphertext failed");
+ err = 1;
+ }
+ }
+
+ /* Each update is a data unit of its own with the same tweak. */
+ if (err == 0) {
+ err = (ctx = EVP_CIPHER_CTX_new()) == NULL;
+ }
+ if (err == 0) {
+ err = EVP_CipherInit_ex2(ctx, wcipher, key, iv, 1, NULL) != 1;
+ }
+ if (err == 0) {
+ err = EVP_CipherUpdate(ctx, wEnc, &outLen, msg, 33) != 1;
+ }
+ if (err == 0) {
+ err = EVP_CipherUpdate(ctx, wEnc + 33, &outLen, msg + 33, 47) != 1;
+ }
+ if (err == 0) {
+ err = test_cipher_xts_crypt(ocipher, 1, key, iv, msg, 33, oEnc);
+ }
+ if (err == 0) {
+ err = test_cipher_xts_crypt(ocipher, 1, key, iv, msg + 33, 47, oEnc + 33);
+ }
+ if ((err == 0) && (memcmp(oEnc, wEnc, 33 + 47) != 0)) {
+ PRINT_MSG("Second update did not reuse the tweak");
+ err = 1;
+ }
+
+ /* Changing direction without a new key is rejected, and the context
+ * then refuses to process data. */
+ if ((err == 0) && (EVP_CipherInit_ex2(ctx, NULL, NULL, iv, 0, NULL) == 1)) {
+ PRINT_MSG("Direction change without a key accepted");
+ err = 1;
+ }
+ if ((err == 0) && (EVP_CipherUpdate(ctx, dec, &outLen, wEnc, 33) == 1)) {
+ PRINT_MSG("Update accepted after a rejected direction change");
+ err = 1;
+ }
+
+ /* Less than one block is rejected. */
+ if (err == 0) {
+ err = EVP_CipherInit_ex2(ctx, wcipher, key, iv, 1, NULL) != 1;
+ }
+ if ((err == 0) && (EVP_CipherUpdate(ctx, wEnc, &outLen, msg, 15) == 1)) {
+ PRINT_MSG("15-byte data unit accepted");
+ err = 1;
+ }
+
+ /* Equal key halves are rejected for encryption and decryption. */
+ if (err == 0) {
+ memcpy(key + keyLen / 2, key, keyLen / 2);
+ if (EVP_CipherInit_ex2(ctx, wcipher, key, iv, 1, NULL) == 1) {
+ PRINT_MSG("Equal key halves accepted for encryption");
+ err = 1;
+ }
+ }
+ if ((err == 0) && (EVP_CipherInit_ex2(ctx, wcipher, key, iv, 0, NULL) == 1)) {
+ PRINT_MSG("Equal key halves accepted for decryption");
+ err = 1;
+ }
+
+ EVP_CIPHER_CTX_free(ctx);
+ EVP_CIPHER_free(wcipher);
+ EVP_CIPHER_free(ocipher);
+
+ return err;
+}
+
+int test_aes128_xts(void *data)
+{
+ return test_cipher_xts(data, "AES-128-XTS", 32);
+}
+
+int test_aes256_xts(void *data)
+{
+ return test_cipher_xts(data, "AES-256-XTS", 64);
+}
+
+#endif /* WP_HAVE_AESXTS */
diff --git a/test/unit.c b/test/unit.c
index 2db3f5a0..ce1ec350 100644
--- a/test/unit.c
+++ b/test/unit.c
@@ -314,6 +314,10 @@ TEST_CASE test_case[] = {
TEST_DECL(test_aes128_cts_one_block, NULL),
TEST_DECL(test_aes128_cts_split_init, NULL),
TEST_DECL(test_aes128_cts_one_block_split_init, NULL),
+#endif
+#ifdef WP_HAVE_AESXTS
+ TEST_DECL(test_aes128_xts, NULL),
+ TEST_DECL(test_aes256_xts, NULL),
#endif
TEST_DECL(test_cipher_null_zero, NULL),
#ifdef WP_HAVE_AESGCM
diff --git a/test/unit.h b/test/unit.h
index a315c3a0..b6e09af4 100644
--- a/test/unit.h
+++ b/test/unit.h
@@ -327,6 +327,13 @@ int test_aes128_cts_one_block_split_init(void *data);
#endif
+#ifdef WP_HAVE_AESXTS
+
+int test_aes128_xts(void *data);
+int test_aes256_xts(void *data);
+
+#endif
+
#ifdef WP_HAVE_RANDOM
int test_random(void *data);