Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ IDE/WINVS/.vs/
IDE/WINVS/**/*.user
IDE/WINVS/**/*.vcxproj.filters

examples/openssl_example
examples/openssl_example*
examples/.deps
examples/.dirstamp
examples/.libs

cscope.out

Expand Down
62 changes: 56 additions & 6 deletions src/wp_aes_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,20 @@ typedef struct wp_AesStreamCtx {
#if defined(WP_HAVE_AESCTS)
/* Only single shot allowed */
unsigned int updated:1;
/** Ciphertext stealing variant in use. */
int ctsMode;
#endif
} wp_AesStreamCtx;


#if defined(WP_HAVE_AESCTS)
/** Ciphertext stealing variants. The last two blocks are swapped in CS3, and
* in CS2 only when the final block is short. */
#define WP_CTS_MODE_CS1 1
#define WP_CTS_MODE_CS2 2
#define WP_CTS_MODE_CS3 3
#endif

/* Prototype for initialization to call. */
static int wp_aes_stream_set_ctx_params(wp_AesStreamCtx *ctx,
const OSSL_PARAM params[]);
Expand Down Expand Up @@ -402,6 +412,15 @@ static int wp_aes_stream_dinit(wp_AesStreamCtx *ctx, const unsigned char *key,

#ifdef WP_HAVE_AESCTS

/* CS3 always swaps the final two blocks, CS2 only when the last block is
* short, and CS1 never does. */
static int wp_aes_cts_swapped(const wp_AesStreamCtx *ctx, size_t partialSz)
{
return (ctx->ctsMode == WP_CTS_MODE_CS3) ||
((ctx->ctsMode == WP_CTS_MODE_CS2) &&
(partialSz != AES_BLOCK_SIZE));
}

static int wp_aes_cts_encrypt(wp_AesStreamCtx *ctx, unsigned char *out,
const unsigned char *in, size_t inLen)
{
Expand Down Expand Up @@ -457,8 +476,17 @@ static int wp_aes_cts_encrypt(wp_AesStreamCtx *ctx, unsigned char *out,
}
}
if (ok) {
XMEMCPY(out, ctsBlock + AES_BLOCK_SIZE, AES_BLOCK_SIZE);
XMEMCPY(out + AES_BLOCK_SIZE, ctsBlock, inLen - AES_BLOCK_SIZE);
size_t partialSz = inLen - AES_BLOCK_SIZE;

if (wp_aes_cts_swapped(ctx, partialSz)) {
XMEMCPY(out, ctsBlock + AES_BLOCK_SIZE, AES_BLOCK_SIZE);
XMEMCPY(out + AES_BLOCK_SIZE, ctsBlock, partialSz);
}
else {
XMEMCPY(out, ctsBlock, partialSz);
XMEMCPY(out + partialSz, ctsBlock + AES_BLOCK_SIZE,
AES_BLOCK_SIZE);
}
}

OPENSSL_cleanse(ctsBlock, sizeof(ctsBlock));
Expand Down Expand Up @@ -518,7 +546,13 @@ static int wp_aes_cts_decrypt(wp_AesStreamCtx *ctx, unsigned char *out,
}
}
if (ok) {
XMEMCPY(ctsBlock, in, inLen);
if (wp_aes_cts_swapped(ctx, partialSz)) {
XMEMCPY(ctsBlock, in, inLen);
}
else {
XMEMCPY(ctsBlock, in + partialSz, AES_BLOCK_SIZE);
XMEMCPY(ctsBlock + AES_BLOCK_SIZE, in, partialSz);
}
XMEMCPY(&ctx->aes.reg, ctsBlock + AES_BLOCK_SIZE, AES_BLOCK_SIZE);
rc = wc_AesCbcDecrypt(&ctx->aes, tmp, ctsBlock, AES_BLOCK_SIZE);
if (rc != 0) {
Expand Down Expand Up @@ -804,7 +838,9 @@ static int wp_aes_stream_get_ctx_params(wp_AesStreamCtx* ctx,
#ifdef WP_HAVE_AESCTS
if (ok && ctx->mode == EVP_CIPH_CBC_MODE) {
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_CTS_MODE);
if ((p != NULL) && (!OSSL_PARAM_set_utf8_string(p, "CS3"))) {
if ((p != NULL) && (!OSSL_PARAM_set_utf8_string(p,
(ctx->ctsMode == WP_CTS_MODE_CS1) ? "CS1" :
(ctx->ctsMode == WP_CTS_MODE_CS2) ? "CS2" : "CS3"))) {
ok = 0;
}
}
Expand Down Expand Up @@ -858,8 +894,19 @@ static int wp_aes_stream_set_ctx_params(wp_AesStreamCtx *ctx,
sizeof(cts_mode))) {
ok = 0;
}
if (ok && (XSTRCMP(cts_mode, "CS3") != 0)) {
ok = 0; /* Only CS3 supported */
if (ok) {
if (XSTRCMP(cts_mode, "CS1") == 0) {
ctx->ctsMode = WP_CTS_MODE_CS1;
}
else if (XSTRCMP(cts_mode, "CS2") == 0) {
ctx->ctsMode = WP_CTS_MODE_CS2;
}
else if (XSTRCMP(cts_mode, "CS3") == 0) {
ctx->ctsMode = WP_CTS_MODE_CS3;
}
else {
ok = 0;
}
}
}
}
Expand All @@ -886,6 +933,9 @@ static void wp_aes_stream_init_ctx(wp_AesStreamCtx* ctx, size_t kBits,
ctx->keyLen = ((kBits) / 8);
ctx->ivLen = ((ivBits) / 8);
ctx->mode = mode;
#if defined(WP_HAVE_AESCTS)
ctx->ctsMode = WP_CTS_MODE_CS1;
#endif
}


Expand Down
200 changes: 193 additions & 7 deletions test/test_cipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -1611,13 +1611,8 @@ int test_aes128_cts_split_init(void *data)
err = (ctx = EVP_CIPHER_CTX_new()) == NULL;
}
if (err == 0) {
OSSL_PARAM params[2];

params[0] = OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE,
(char *)"CS3", 0);
params[1] = OSSL_PARAM_construct_end();

err = EVP_CipherInit_ex2(ctx, ocipher, key, iv, 1, params) != 1
/* No variant requested, so both providers use their default. */
err = EVP_CipherInit_ex2(ctx, ocipher, key, iv, 1, NULL) != 1
|| EVP_CipherUpdate(ctx, ref, &outlen, msg, sizeof(msg)) != 1
|| EVP_CipherFinal_ex(ctx, ref + outlen, &finallen) != 1;
}
Expand Down Expand Up @@ -1784,6 +1779,197 @@ int test_aes128_cts_one_block_split_init(void *data)
return err;
}

/**
* Encrypt with the given provider and ciphertext stealing variant.
*
* @param [in] cipher Cipher fetched from the provider under test.
* @param [in] ctsMode Variant name, CS1, CS2 or CS3.
* @param [in] key Key data.
* @param [in] iv IV data.
* @param [in] msg Plaintext.
* @param [in] len Length of plaintext in bytes.
* @param [out] enc Buffer for the ciphertext.
* @param [out] encLen Length of ciphertext in bytes.
* @return 0 on success, non-zero on failure.
*/
static int test_cipher_cts_mode_enc(const EVP_CIPHER *cipher,
const char *ctsMode, unsigned char *key, unsigned char *iv,
unsigned char *msg, int len, unsigned char *enc, int *encLen)
{
int err;
EVP_CIPHER_CTX *ctx = NULL;
OSSL_PARAM params[2];
int outLen = 0;
int total = 0;

params[0] = OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE,
(char *)ctsMode, 0);
params[1] = OSSL_PARAM_construct_end();

err = (ctx = EVP_CIPHER_CTX_new()) == NULL;
if (err == 0) {
err = EVP_CipherInit_ex2(ctx, cipher, key, iv, 1, params) != 1;
}
if (err == 0) {
err = EVP_CipherUpdate(ctx, enc, &outLen, msg, len) != 1;
total = outLen;
}
if (err == 0) {
err = EVP_CipherFinal_ex(ctx, enc + total, &outLen) != 1;
total += outLen;
}
if (err == 0) {
*encLen = total;
}

EVP_CIPHER_CTX_free(ctx);
return err;
}

/**
* Decrypt with the given provider and ciphertext stealing variant.
*/
static int test_cipher_cts_mode_dec(const EVP_CIPHER *cipher,
const char *ctsMode, unsigned char *key, unsigned char *iv,
unsigned char *enc, int encLen, unsigned char *dec, int *decLen)
{
int err;
EVP_CIPHER_CTX *ctx = NULL;
OSSL_PARAM params[2];
int outLen = 0;
int total = 0;

params[0] = OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE,
(char *)ctsMode, 0);
params[1] = OSSL_PARAM_construct_end();

err = (ctx = EVP_CIPHER_CTX_new()) == NULL;
if (err == 0) {
err = EVP_CipherInit_ex2(ctx, cipher, key, iv, 0, params) != 1;
}
if (err == 0) {
err = EVP_CipherUpdate(ctx, dec, &outLen, enc, encLen) != 1;
total = outLen;
}
if (err == 0) {
err = EVP_CipherFinal_ex(ctx, dec + total, &outLen) != 1;
total += outLen;
}
if (err == 0) {
*decLen = total;
}

EVP_CIPHER_CTX_free(ctx);
return err;
}

/**
* Compare AES-CTS against the OpenSSL default provider for one variant.
*
* The variants differ only in the order of the final two ciphertext blocks,
* so a mismatch means data written by one provider cannot be read by the
* other. Lengths cover a short final block and an exact block multiple.
*/
static int test_cipher_cts_mode_helper(const char *cipherName,
const char *ctsMode, int keyLen)
{
static const int lengths[] = { 17, 31, 33, 64 };
int err = 0;
size_t i;
EVP_CIPHER *wcipher = NULL;
EVP_CIPHER *ocipher = NULL;
unsigned char key[32];
unsigned char iv[16];
unsigned char msg[64];
unsigned char wEnc[128];
unsigned char oEnc[128];
unsigned char dec[128];
int wLen = 0;
int oLen = 0;
int decLen = 0;

memset(key, 0xA5, (size_t)keyLen);
memset(iv, 0x5A, sizeof(iv));
for (i = 0; i < sizeof(msg); i++) {
msg[i] = (unsigned char)i;
}

wcipher = EVP_CIPHER_fetch(wpLibCtx, cipherName, "");
ocipher = EVP_CIPHER_fetch(osslLibCtx, cipherName, "");
err = (wcipher == NULL) || (ocipher == NULL);

for (i = 0; (err == 0) && (i < sizeof(lengths) / sizeof(lengths[0]));
i++) {
int len = lengths[i];

PRINT_MSG("%s %s length %d", cipherName, ctsMode, len);

err = test_cipher_cts_mode_enc(wcipher, ctsMode, key, iv, msg, len,
wEnc, &wLen);
if (err == 0) {
err = test_cipher_cts_mode_enc(ocipher, ctsMode, key, iv, msg,
len, oEnc, &oLen);
}
if (err == 0) {
err = (wLen != oLen) || (memcmp(wEnc, oEnc, (size_t)wLen) != 0);
if (err) {
PRINT_ERR_MSG("%s %s length %d: ciphertext differs",
cipherName, ctsMode, len);
}
}
/* Each provider must read what the other wrote. */
if (err == 0) {
err = test_cipher_cts_mode_dec(ocipher, ctsMode, key, iv, wEnc,
wLen, dec, &decLen);
}
if (err == 0) {
err = (decLen != len) || (memcmp(dec, msg, (size_t)len) != 0);
if (err) {
PRINT_ERR_MSG("%s %s length %d: OpenSSL decrypt mismatch",
cipherName, ctsMode, len);
}
}
if (err == 0) {
err = test_cipher_cts_mode_dec(wcipher, ctsMode, key, iv, oEnc,
oLen, dec, &decLen);
}
if (err == 0) {
err = (decLen != len) || (memcmp(dec, msg, (size_t)len) != 0);
if (err) {
PRINT_ERR_MSG("%s %s length %d: wolfProvider decrypt mismatch",
cipherName, ctsMode, len);
}
}
}

EVP_CIPHER_free(wcipher);
EVP_CIPHER_free(ocipher);
return err;
}

/**
* Test every ciphertext stealing variant against the OpenSSL default
* provider, including the CS1 default used when no variant is requested.
*/
int test_aes_cts_modes(void *data)
{
static const char *modes[] = { "CS1", "CS2", "CS3" };
int err = 0;
size_t i;

(void)data;

for (i = 0; (err == 0) && (i < sizeof(modes) / sizeof(modes[0])); i++) {
err = test_cipher_cts_mode_helper("AES-128-CBC-CTS", modes[i], 16);
if (err == 0) {
err = test_cipher_cts_mode_helper("AES-256-CBC-CTS", modes[i],
32);
}
}

return err;
}

#endif /* WP_HAVE_AESCTS */

#ifdef WP_HAVE_AESCBC
Expand Down
1 change: 1 addition & 0 deletions test/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ TEST_CASE test_case[] = {
#ifdef WP_HAVE_AESCTS
TEST_DECL(test_aes128_cts, NULL),
TEST_DECL(test_aes256_cts, NULL),
TEST_DECL(test_aes_cts_modes, NULL),
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),
Expand Down
1 change: 1 addition & 0 deletions test/unit.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ int test_aes_ccm_key_no_iv(void *data);

int test_aes128_cts(void *data);
int test_aes256_cts(void *data);
int test_aes_cts_modes(void *data);
int test_aes128_cts_one_block(void *data);
int test_aes128_cts_split_init(void *data);
int test_aes128_cts_one_block_split_init(void *data);
Expand Down
Loading