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);