diff --git a/include/wolfprovider/alg_funcs.h b/include/wolfprovider/alg_funcs.h index 4c8bf2a4..3133044a 100644 --- a/include/wolfprovider/alg_funcs.h +++ b/include/wolfprovider/alg_funcs.h @@ -120,6 +120,10 @@ typedef void (*DFUNC)(void); #define WP_NAMES_AES_128_WRAP \ "AES-128-WRAP:id-aes128-wrap:AES128-WRAP:2.16.840.1.101.3.4.1.5" +#define WP_NAMES_AES_256_CTS "AES-256-CBC-CTS" +#define WP_NAMES_AES_192_CTS "AES-192-CBC-CTS" +#define WP_NAMES_AES_128_CTS "AES-128-CBC-CTS" + #define WP_NAMES_DES_EDE3_CBC "DES-EDE3-CBC:DES3:1.2.840.113549.3.7" /* Internal cipher flags. */ @@ -288,6 +292,10 @@ extern const OSSL_DISPATCH wp_aes256wrap_functions[]; extern const OSSL_DISPATCH wp_aes192wrap_functions[]; extern const OSSL_DISPATCH wp_aes128wrap_functions[]; +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_des3cbc_functions[]; /* MAC implementations. */ diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h index 34aa7290..b5ab88c7 100644 --- a/include/wolfprovider/settings.h +++ b/include/wolfprovider/settings.h @@ -81,6 +81,7 @@ #endif #ifndef NO_AES_CBC #define WP_HAVE_AESCBC + #define WP_HAVE_AESCTS #endif #ifndef NO_DES3 #define WP_HAVE_DES3CBC diff --git a/src/wp_aes_stream.c b/src/wp_aes_stream.c index 4dc840ce..26bde833 100644 --- a/src/wp_aes_stream.c +++ b/src/wp_aes_stream.c @@ -29,7 +29,7 @@ #include #include -#if defined(WP_HAVE_AESCTR) || defined(WP_HAVE_AESCFB) +#if defined(WP_HAVE_AESCTR) || defined(WP_HAVE_AESCFB) || defined(WP_HAVE_AESCTS) /** * Data structure for AES ciphers that are streaming. @@ -38,7 +38,7 @@ typedef struct wp_AesStreamCtx { /** wolfSSL AES object. */ Aes aes; - /** Cipher mode - CTR. */ + /** Cipher mode - CTR, CFB or CTS. */ int mode; /** Length of key in bytes. */ @@ -53,6 +53,11 @@ typedef struct wp_AesStreamCtx { unsigned char iv[AES_BLOCK_SIZE]; /** Original IV. */ unsigned char oiv[AES_BLOCK_SIZE]; + +#if defined(WP_HAVE_AESCTS) + /* Only single shot allowed */ + unsigned int updated:1; +#endif } wp_AesStreamCtx; @@ -194,6 +199,9 @@ static const OSSL_PARAM* wp_cipher_gettable_ctx_params(wp_AesStreamCtx* ctx, OSSL_PARAM_uint(OSSL_CIPHER_PARAM_NUM, NULL), OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_IV, NULL, 0), OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_UPDATED_IV, NULL, 0), +#ifdef WP_HAVE_AESCTS + OSSL_PARAM_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, NULL, 0), +#endif OSSL_PARAM_END }; (void)ctx; @@ -217,6 +225,9 @@ static const OSSL_PARAM* wp_cipher_settable_ctx_params(wp_AesStreamCtx* ctx, static const OSSL_PARAM wp_cipher_supported_settable_ctx_params[] = { OSSL_PARAM_uint(OSSL_CIPHER_PARAM_NUM, NULL), OSSL_PARAM_uint(OSSL_CIPHER_PARAM_USE_BITS, NULL), +#ifdef WP_HAVE_AESCTS + OSSL_PARAM_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, NULL, 0), +#endif OSSL_PARAM_END }; (void)ctx; @@ -270,6 +281,8 @@ static int wp_aes_stream_init(wp_AesStreamCtx *ctx, const unsigned char *key, const OSSL_PARAM params[], int enc) { int ok = 1; + /* Decryption is the same as encryption with CTR mode. */ + int dir = AES_ENCRYPTION; ctx->enc = enc; @@ -286,9 +299,13 @@ static int wp_aes_stream_init(wp_AesStreamCtx *ctx, const unsigned char *key, ok = 0; } if (ok) { - /* Decryption is the same as encryption with CTR mode. */ +#if defined(WP_HAVE_AESCTS) + if (ctx->mode == EVP_CIPH_CBC_MODE && !enc) { + dir = AES_DECRYPTION; + } +#endif int rc = wc_AesSetKey(&ctx->aes, key, (word32)ctx->keyLen, iv, - AES_ENCRYPTION); + dir); if (rc != 0) { ok = 0; } @@ -302,6 +319,10 @@ static int wp_aes_stream_init(wp_AesStreamCtx *ctx, const unsigned char *key, } if (ok) { +#if defined(WP_HAVE_AESCTS) + /* We only allow one shot, always reset on init */ + ctx->updated = 0; +#endif ok = wp_aes_stream_set_ctx_params(ctx, params); } @@ -347,8 +368,121 @@ static int wp_aes_stream_dinit(wp_AesStreamCtx *ctx, const unsigned char *key, return wp_aes_stream_init(ctx, key, keyLen, iv, ivLen, params, 0); } +#ifdef WP_HAVE_AESCTS + +static int wp_aes_cts_encrypt(wp_AesStreamCtx *ctx, unsigned char *out, + const unsigned char *in, size_t inLen) +{ + int ok = 1; + int rc; + int blocks; + byte ctsBlock[AES_BLOCK_SIZE * 2]; + + /* Since AES-CTS is not a FIPS approved algo, we will never be able to call + * the existing wolfSSL AES_CTS APIs with FIPS, so the implementation is + * effectively copied here from wolfSSL internals. */ + + blocks = (int)((inLen + (AES_BLOCK_SIZE - 1)) / AES_BLOCK_SIZE); + blocks -= 2; + XMEMSET(ctsBlock, 0, AES_BLOCK_SIZE * 2); + if (ok && blocks > 0) { + XMEMCPY(&ctx->aes.reg, ctx->iv, ctx->ivLen); + rc = wc_AesCbcEncrypt(&ctx->aes, out, in, blocks * AES_BLOCK_SIZE); + if (rc != 0) { + ok = 0; + } + if (ok) { + XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); + in += blocks * AES_BLOCK_SIZE; + out += blocks * AES_BLOCK_SIZE; + inLen -= blocks * AES_BLOCK_SIZE; + } + } + if (ok) { + XMEMCPY(ctsBlock, in, inLen); + rc = wc_AesCbcEncrypt(&ctx->aes, ctsBlock, ctsBlock, + AES_BLOCK_SIZE * 2); + if (rc != 0) { + ok = 0; + } + if (ok) { + XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); + } + } + if (ok) { + XMEMCPY(out, ctsBlock + AES_BLOCK_SIZE, AES_BLOCK_SIZE); + XMEMCPY(out + AES_BLOCK_SIZE, ctsBlock, AES_BLOCK_SIZE); + } + + return ok; +} + +static int wp_aes_cts_decrypt(wp_AesStreamCtx *ctx, unsigned char *out, + const unsigned char *in, size_t inLen) +{ + int ok = 1; + int rc; + int blocks; + byte ctsBlock[AES_BLOCK_SIZE * 2]; + byte tmp[AES_BLOCK_SIZE]; + word32 partialSz; + word32 padSz; + + /* Since AES-CTS is not a FIPS approved algo, we will never be able to call + * the existing wolfSSL AES_CTS APIs with FIPS, so the implementation is + * effectively copied here from wolfSSL internals. */ + + partialSz = inLen % AES_BLOCK_SIZE; + if (partialSz == 0) { + partialSz = AES_BLOCK_SIZE; + } + padSz = AES_BLOCK_SIZE - partialSz; + + blocks = (int)((inLen + (AES_BLOCK_SIZE - 1)) / AES_BLOCK_SIZE); + blocks -= 2; + XMEMSET(ctsBlock, 0, AES_BLOCK_SIZE * 2); + if (ok && blocks > 0) { + XMEMCPY(&ctx->aes.reg, ctx->iv, ctx->ivLen); + rc = wc_AesCbcDecrypt(&ctx->aes, out, in, blocks * AES_BLOCK_SIZE); + if (rc != 0) { + ok = 0; + } + if (ok) { + XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); + in += blocks * AES_BLOCK_SIZE; + out += blocks * AES_BLOCK_SIZE; + inLen -= blocks * AES_BLOCK_SIZE; + } + } + if (ok) { + XMEMCPY(ctsBlock, in, inLen); + XMEMCPY(&ctx->aes.reg, ctsBlock + AES_BLOCK_SIZE, AES_BLOCK_SIZE); + rc = wc_AesCbcDecrypt(&ctx->aes, tmp, ctsBlock, AES_BLOCK_SIZE); + if (rc != 0) { + ok = 0; + } + } + if (ok) { + XMEMCPY(out + AES_BLOCK_SIZE, tmp, partialSz); + XMEMCPY(ctsBlock + inLen, tmp + partialSz, padSz); + XMEMCPY(&ctx->aes.reg, ctx->iv, AES_BLOCK_SIZE); + rc = wc_AesCbcDecrypt(&ctx->aes, out, ctsBlock + AES_BLOCK_SIZE, + AES_BLOCK_SIZE); + if (rc != 0) { + ok = 0; + } + if (ok) { + XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); + } + } + + return ok; +} + +#endif /* ifdef WP_HAVE_AESCTS */ + /** - * Encrypt/decrypt using AES-CTR or AES-CFB with wolfSSL. + * Encrypt/decrypt using AES-CTR, AES-CFB or AES-CTS with wolfSSL. * * Assumes out has inLen bytes available. * Assumes whole blocks only. @@ -363,7 +497,7 @@ static int wp_aes_stream_dinit(wp_AesStreamCtx *ctx, const unsigned char *key, static int wp_aes_stream_doit(wp_AesStreamCtx *ctx, unsigned char *out, const unsigned char *in, size_t inLen) { - int ok = 0; + int ok = 1; #ifdef WP_HAVE_AESCTR if (ctx->mode == EVP_CIPH_CTR_MODE) { @@ -371,9 +505,11 @@ static int wp_aes_stream_doit(wp_AesStreamCtx *ctx, unsigned char *out, XMEMCPY(&ctx->aes.reg, ctx->iv, ctx->ivLen); rc = wc_AesCtrEncrypt(&ctx->aes, out, in, (word32)inLen); - if (rc == 0) { + if (rc != 0) { + ok = 0; + } + if (ok) { XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); - ok = 1; } } else @@ -388,9 +524,33 @@ static int wp_aes_stream_doit(wp_AesStreamCtx *ctx, unsigned char *out, }else { rc = wc_AesCfbDecrypt(&ctx->aes, out, in, (word32)inLen); } - if (rc == 0) { + if (rc != 0) { + ok = 0; + } + if (ok) { XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); - ok = 1; + } + } + else +#endif +#ifdef WP_HAVE_AESCTS + if (ctx->mode == EVP_CIPH_CBC_MODE) { + if (ctx->updated) { + ok = 0; + } + if (inLen < AES_BLOCK_SIZE) { + ok = 0; + } + if (ok) { + if (ctx->enc) { + ok = wp_aes_cts_encrypt(ctx, out, in, inLen); + } + else { + ok = wp_aes_cts_decrypt(ctx, out, in, inLen); + } + } + if (ok) { + ctx->updated = 1; } } else @@ -534,6 +694,14 @@ static int wp_aes_stream_get_ctx_params(wp_AesStreamCtx* ctx, ok = 0; } } +#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"))) { + ok = 0; + } + } +#endif WOLFPROV_LEAVE(WP_LOG_CIPHER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; @@ -568,6 +736,25 @@ static int wp_aes_stream_set_ctx_params(wp_AesStreamCtx *ctx, ok = 0; } (void)val; +#ifdef WP_HAVE_AESCTS + if (ok && ctx->mode == EVP_CIPH_CBC_MODE) { + char cts_mode[4]; + char *pcts = cts_mode; + const OSSL_PARAM* p = NULL; + XMEMSET(cts_mode, 0, sizeof(cts_mode)); + + p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_CTS_MODE); + if (p != NULL) { + if (!OSSL_PARAM_get_utf8_string(p, &pcts, + sizeof(cts_mode))) { + ok = 0; + } + if (ok && (XSTRCMP(cts_mode, "CS3") != 0)) { + ok = 0; /* Only CS3 supported */ + } + } + } +#endif } WOLFPROV_LEAVE(WP_LOG_CIPHER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); @@ -580,7 +767,7 @@ static int wp_aes_stream_set_ctx_params(wp_AesStreamCtx *ctx, * @param [in, out] ctx AES stream context object. * @param [in] kBits Number of bits in a valid key. * @param [in] ivBits Number of bits in a valid IV. 0 indicates no IV. - * @param [in] mode AES stream mode: CTR. + * @param [in] mode AES stream mode: CTR, CFB or CTS. * @return 1 on success. * @return 0 on failure. */ @@ -685,5 +872,20 @@ IMPLEMENT_AES_STREAM(cfb, CFB, 192, 128) IMPLEMENT_AES_STREAM(cfb, CFB, 128, 128) #endif /* WP_HAVE_AESCFB */ -#endif /* WP_HAVE_AESCTR || WP_HAVE_AESCFB */ +/* + * AES CTS + * + * Even though AES-CTS is a block cipher, since we will only be supporting a + * single-shot mode it actually behaves more like a stream cipher. + */ +#ifdef WP_HAVE_AESCTS +/** wp_aes256cts_functions */ +IMPLEMENT_AES_STREAM(cts, CBC, 256, 128) +/** wp_aes192cts_functions */ +IMPLEMENT_AES_STREAM(cts, CBC, 192, 128) +/** wp_aes128cts_functions */ +IMPLEMENT_AES_STREAM(cts, CBC, 128, 128) +#endif /* WP_HAVE_AESCTS */ + +#endif /* WP_HAVE_AESCTR || WP_HAVE_AESCFB || WP_HAVE_AESCTS */ diff --git a/src/wp_wolfprov.c b/src/wp_wolfprov.c index 55b47b5d..7c5428d6 100644 --- a/src/wp_wolfprov.c +++ b/src/wp_wolfprov.c @@ -482,6 +482,16 @@ static const OSSL_ALGORITHM wolfprov_ciphers[] = { "" }, #endif +#ifdef WP_HAVE_AESCTS + /* AES-CTS */ + { WP_NAMES_AES_256_CTS, WOLFPROV_PROPERTIES, wp_aes256cts_functions, + "" }, + { WP_NAMES_AES_192_CTS, WOLFPROV_PROPERTIES, wp_aes192cts_functions, + "" }, + { WP_NAMES_AES_128_CTS, WOLFPROV_PROPERTIES, wp_aes128cts_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 6888ce4b..aa8dcb3f 100644 --- a/test/test_cipher.c +++ b/test/test_cipher.c @@ -22,7 +22,7 @@ #if defined(WP_HAVE_DES3CBC) || defined(WP_HAVE_AESCBC) || \ defined(WP_HAVE_AESECB) || defined(WP_HAVE_AESCTR) || \ - defined(WP_HAVE_AESCFB) + defined(WP_HAVE_AESCFB) || defined(WP_HAVE_AESCTS) static int test_cipher_enc(const EVP_CIPHER *cipher, unsigned char *key, unsigned char *iv, @@ -181,7 +181,7 @@ static int test_cipher_enc_dec(void *data, const char *cipher, int keyLen, #if defined(WP_HAVE_DES3CBC) || defined(WP_HAVE_AESCBC) || \ defined(WP_HAVE_AESECB) || defined(WP_HAVE_AESCTR) || \ - defined(WP_HAVE_AESCFB) + defined(WP_HAVE_AESCFB) || defined(WP_HAVE_AESCTS) /******************************************************************************/ @@ -671,3 +671,504 @@ int test_aes256_cfb_stream(void *data) #endif /* WP_HAVE_AESCFB */ +#ifdef WP_HAVE_AESCTS + +static int test_cipher_cts_enc_err_cases(const EVP_CIPHER *cipher, + unsigned char *key, unsigned char *iv, + unsigned char *msg, int len, unsigned char *enc) +{ + int err; + EVP_CIPHER_CTX *ctx; + int encLen; + int ret; + + (void)len; /* Length from caller not used in error cases */ + + /* Test case 1: Input less than block length - should fail */ + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + if (err == 0) { + err = EVP_EncryptInit(ctx, cipher, key, iv) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_set_padding(ctx, 0) != 1; + } + if (err == 0) { + /* Try to encrypt 5 bytes - should fail */ + ret = EVP_EncryptUpdate(ctx, enc, &encLen, msg, 5); + if (ret == 1) { + PRINT_MSG("ERROR: Encryption succeeded with short message when it should fail"); + err = 1; + } + else { + PRINT_MSG("SUCCESS: Encryption correctly failed with short message"); + } + } + EVP_CIPHER_CTX_free(ctx); + + /* Test case 2: Double update not allowed */ + if (err == 0) { + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, cipher, key, iv) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_set_padding(ctx, 0) != 1; + } + /* First update should succeed */ + if (err == 0) { + err = EVP_EncryptUpdate(ctx, enc, &encLen, msg, 17) != 1; + PRINT_MSG("First update of 17 bytes succeeded as expected"); + } + /* Second update should fail */ + if (err == 0) { + ret = EVP_EncryptUpdate(ctx, enc + encLen, &encLen, msg + 17, 17); + if (ret == 1) { + PRINT_MSG("ERROR: Second update succeeded when it should fail"); + err = 1; + } + else { + PRINT_MSG("SUCCESS: Second update correctly failed"); + } + } + EVP_CIPHER_CTX_free(ctx); + + return err; +} + +static int test_cipher_cts_krb_enc(const EVP_CIPHER *cipher, + unsigned char *key, unsigned char *iv, + unsigned char *msg, int len, unsigned char *enc) +{ + int err; + EVP_CIPHER_CTX *ctx; + int outlen, total_len = 0; + unsigned char iv_cts[16]; + OSSL_PARAM params[2]; + + memset(iv_cts, 0, sizeof(iv_cts)); + if (iv != NULL) { + memcpy(iv_cts, iv, sizeof(iv_cts)); + } + + /* First encryption run */ + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + if (err == 0) { + /* Set up parameters for CS3 mode */ + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, + (char *)"CS3", 0); + params[1] = OSSL_PARAM_construct_end(); + + /* Initialize with cipher and key only first */ + err = EVP_CipherInit_ex2(ctx, cipher, key, NULL, 1, params) != 1; + } + if (err == 0) { + /* Set IV and get updated IV */ + err = EVP_CipherUpdate(ctx, enc, &outlen, msg, len) != 1; + total_len = outlen; + } + if (err == 0) { + err = EVP_CipherFinal_ex(ctx, enc + total_len, &outlen) != 1; + total_len += outlen; + } + if (err == 0) { + /* Get the updated IV */ + err = EVP_CIPHER_CTX_get_updated_iv(ctx, iv_cts, sizeof(iv_cts)) != 1; + } + + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + if (err == 0) { + PRINT_BUFFER("KRB CTS Encrypted", enc, total_len); + if (iv != NULL) { + PRINT_BUFFER("Updated IV", iv_cts, sizeof(iv_cts)); + } + } + + return err; +} + +static int test_cipher_cts_krb_dec(const EVP_CIPHER *cipher, + unsigned char *key, unsigned char *iv, + unsigned char *msg, int len, unsigned char *enc, + int encLen, unsigned char *dec) +{ + int err; + EVP_CIPHER_CTX *ctx; + int outlen, total_len = 0; + unsigned char iv_cts[16]; + OSSL_PARAM params[2]; + + memset(iv_cts, 0, sizeof(iv_cts)); + if (iv != NULL) { + memcpy(iv_cts, iv, sizeof(iv_cts)); + } + + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + if (err == 0) { + /* Set up parameters for CS3 mode */ + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, + (char *)"CS3", 0); + params[1] = OSSL_PARAM_construct_end(); + + /* Initialize with cipher and key only first */ + err = EVP_CipherInit_ex2(ctx, cipher, key, NULL, 0, params) != 1; + } + if (err == 0) { + /* Set IV and decrypt */ + err = EVP_CipherUpdate(ctx, dec, &outlen, enc, encLen) != 1; + total_len = outlen; + } + if (err == 0) { + err = EVP_CipherFinal_ex(ctx, dec + total_len, &outlen) != 1; + total_len += outlen; + } + if (err == 0) { + /* Get the updated IV if needed */ + err = EVP_CIPHER_CTX_get_updated_iv(ctx, iv_cts, sizeof(iv_cts)) != 1; + } + + if (err == 0) { + PRINT_BUFFER("KRB CTS Decrypted", dec, total_len); + if (iv != NULL) { + PRINT_BUFFER("Updated IV", iv_cts, sizeof(iv_cts)); + } + + if (total_len != len || memcmp(dec, msg, len) != 0) { + PRINT_MSG("KRB CTS Decryption mismatch"); + err = 1; + } + } + + EVP_CIPHER_CTX_free(ctx); + + return err; +} + +static int test_cipher_cts_kat(const EVP_CIPHER* cipher, unsigned char* key) +{ + int err = 0; + /* Test vectors taken from RFC3962 Appendix B */ + const struct { + const char* input; + const char* output; + size_t inLen; + size_t outLen; + } vects[] = { + { + "\x49\x20\x77\x6f\x75\x6c\x64\x20\x6c\x69\x6b\x65\x20\x74\x68\x65" + "\x20", + "\xc6\x35\x35\x68\xf2\xbf\x8c\xb4\xd8\xa5\x80\x36\x2d\xa7\xff\x7f" + "\x97", + 17, 17 + }, + { + "\x49\x20\x77\x6f\x75\x6c\x64\x20\x6c\x69\x6b\x65\x20\x74\x68\x65" + "\x20\x47\x65\x6e\x65\x72\x61\x6c\x20\x47\x61\x75\x27\x73\x20", + "\xfc\x00\x78\x3e\x0e\xfd\xb2\xc1\xd4\x45\xd4\xc8\xef\xf7\xed\x22" + "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5", + 31, 31 + }, + { + "\x49\x20\x77\x6f\x75\x6c\x64\x20\x6c\x69\x6b\x65\x20\x74\x68\x65" + "\x20\x47\x65\x6e\x65\x72\x61\x6c\x20\x47\x61\x75\x27\x73\x20\x43", + "\x39\x31\x25\x23\xa7\x86\x62\xd5\xbe\x7f\xcb\xcc\x98\xeb\xf5\xa8" + "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5\x84", + 32, 32 + }, + { + "\x49\x20\x77\x6f\x75\x6c\x64\x20\x6c\x69\x6b\x65\x20\x74\x68\x65" + "\x20\x47\x65\x6e\x65\x72\x61\x6c\x20\x47\x61\x75\x27\x73\x20\x43" + "\x68\x69\x63\x6b\x65\x6e\x2c\x20\x70\x6c\x65\x61\x73\x65\x2c", + "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5\x84" + "\xb3\xff\xfd\x94\x0c\x16\xa1\x8c\x1b\x55\x49\xd2\xf8\x38\x02\x9e" + "\x39\x31\x25\x23\xa7\x86\x62\xd5\xbe\x7f\xcb\xcc\x98\xeb\xf5", + 47, 47 + }, + { + "\x49\x20\x77\x6f\x75\x6c\x64\x20\x6c\x69\x6b\x65\x20\x74\x68\x65" + "\x20\x47\x65\x6e\x65\x72\x61\x6c\x20\x47\x61\x75\x27\x73\x20\x43" + "\x68\x69\x63\x6b\x65\x6e\x2c\x20\x70\x6c\x65\x61\x73\x65\x2c\x20", + "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5\x84" + "\x9d\xad\x8b\xbb\x96\xc4\xcd\xc0\x3b\xc1\x03\xe1\xa1\x94\xbb\xd8" + "\x39\x31\x25\x23\xa7\x86\x62\xd5\xbe\x7f\xcb\xcc\x98\xeb\xf5\xa8", + 48, 48 + }, + { + "\x49\x20\x77\x6f\x75\x6c\x64\x20\x6c\x69\x6b\x65\x20\x74\x68\x65" + "\x20\x47\x65\x6e\x65\x72\x61\x6c\x20\x47\x61\x75\x27\x73\x20\x43" + "\x68\x69\x63\x6b\x65\x6e\x2c\x20\x70\x6c\x65\x61\x73\x65\x2c\x20" + "\x61\x6e\x64\x20\x77\x6f\x6e\x74\x6f\x6e\x20\x73\x6f\x75\x70\x2e", + "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5\x84" + "\x39\x31\x25\x23\xa7\x86\x62\xd5\xbe\x7f\xcb\xcc\x98\xeb\xf5\xa8" + "\x48\x07\xef\xe8\x36\xee\x89\xa5\x26\x73\x0d\xbc\x2f\x7b\xc8\x40" + "\x9d\xad\x8b\xbb\x96\xc4\xcd\xc0\x3b\xc1\x03\xe1\xa1\x94\xbb\xd8", + 64, 64 + } + }; + unsigned char iv[16] = {0}; + unsigned char iv2[16] = {0}; + unsigned char enc[64]; /* Large enough for biggest test vector */ + unsigned char dec[64]; /* Large enough for biggest test vector */ + int outlen, total_len = 0; + EVP_CIPHER_CTX *ctx; + OSSL_PARAM params[2]; + unsigned char iv_cts[16] = {0}; + size_t i; + + PRINT_MSG("Running CTS Known Answer Tests"); + + /* Run through all test vectors */ + for (i = 0; i < sizeof(vects)/sizeof(vects[0]); i++) { + PRINT_MSG("\nTest Vector %zu", i + 1); + PRINT_BUFFER("Input", (unsigned char*)vects[i].input, vects[i].inLen); + + /* Reset IVs for each test */ + memset(iv, 0, sizeof(iv)); + memset(iv2, 0, sizeof(iv2)); + memset(iv_cts, 0, sizeof(iv_cts)); + + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + if (err == 0) { + /* Set up parameters for CS3 mode */ + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, + (char *)"CS3", 0); + params[1] = OSSL_PARAM_construct_end(); + + /* Initialize with cipher and key only first */ + err = EVP_CipherInit_ex2(ctx, cipher, key, iv, 1, params) != 1; + } + if (err == 0) { + /* Set IV and encrypt */ + err = EVP_CipherUpdate(ctx, enc, &outlen, + (unsigned char*)vects[i].input, + (int)vects[i].inLen) != 1; + total_len = outlen; + } + if (err == 0) { + err = EVP_CipherFinal_ex(ctx, enc + total_len, &outlen) != 1; + total_len += outlen; + } + + if (err == 0) { + PRINT_BUFFER("Output", enc, total_len); + PRINT_BUFFER("Expected", (unsigned char*)vects[i].output, vects[i].outLen); + + /* Compare results */ + if (total_len != (int)vects[i].outLen || + memcmp(enc, vects[i].output, vects[i].outLen) != 0) { + PRINT_MSG("KAT Encryption output mismatch for vector %zu", i + 1); + err = 1; + break; + } + } + + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + /* Now test decryption */ + if (err == 0) { + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + } + if (err == 0) { + 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, cipher, key, iv2, 0, params) != 1; + } + if (err == 0) { + err = EVP_CipherUpdate(ctx, dec, &outlen, + (unsigned char*)vects[i].output, + (int)vects[i].outLen) != 1; + total_len = outlen; + } + if (err == 0) { + err = EVP_CipherFinal_ex(ctx, dec + total_len, &outlen) != 1; + total_len += outlen; + } + + if (err == 0) { + PRINT_BUFFER("Decrypted", dec, total_len); + + /* Compare decryption results */ + if (total_len != (int)vects[i].inLen || + memcmp(dec, vects[i].input, vects[i].inLen) != 0) { + PRINT_MSG("KAT Decryption mismatch for vector %zu", i + 1); + err = 1; + break; + } + } + + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + if (err != 0) break; + } + + return err; +} + +static int test_cipher_cts(void *data, const char *cipher, int keyLen) +{ + int err = 0; + /* Test messages of different sizes: + * 1. Known Answer Test + * 2. One block plus one byte (17 bytes) + * 3. More than two blocks with partial last block (37 bytes) + * 4. Exactly four blocks (64 bytes) + * 5. Error cases + */ + unsigned char msg1[17]; /* One block plus one byte */ + unsigned char msg2[37]; /* More than two blocks with partial last block */ + unsigned char msg3[64]; /* Exactly four blocks */ + unsigned char key[32]; + unsigned char iv[16]; + /* Buffer large enough for largest message */ + unsigned char enc[sizeof(msg3)]; + unsigned char dec[sizeof(msg3)]; + EVP_CIPHER* ocipher; + EVP_CIPHER* wcipher; + + (void)data; + + ocipher = EVP_CIPHER_fetch(osslLibCtx, cipher, ""); + wcipher = EVP_CIPHER_fetch(wpLibCtx, cipher, ""); + + /* Set up KAT key */ + unsigned char kat_key[] = { + 0x63, 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x20, + 0x74, 0x65, 0x72, 0x69, 0x79, 0x61, 0x6b, 0x69 + }; + + /* Generate random key, IV and messages */ + if (RAND_bytes(key, keyLen) != 1) { + err = 1; + } + if (err == 0) { + if (RAND_bytes(iv, sizeof(iv)) != 1) { + err = 1; + } + } + if (err == 0) { + if (RAND_bytes(msg1, sizeof(msg1)) != 1) { + err = 1; + } + } + if (err == 0) { + if (RAND_bytes(msg2, sizeof(msg2)) != 1) { + err = 1; + } + } + if (err == 0) { + if (RAND_bytes(msg3, sizeof(msg3)) != 1) { + err = 1; + } + } + + if (err == 0) { + PRINT_BUFFER("Key", key, keyLen); + PRINT_BUFFER("IV", iv, sizeof(iv)); + PRINT_BUFFER("Message 1", msg1, sizeof(msg1)); + PRINT_BUFFER("Message 2", msg2, sizeof(msg2)); + PRINT_BUFFER("Message 3", msg3, sizeof(msg3)); + } + + /* Run Known Answer Test first */ + if (keyLen == 16) { + if (err == 0) { + PRINT_MSG("Running Known Answer Test with OpenSSL"); + err = test_cipher_cts_kat(ocipher, kat_key); + } + if (err == 0) { + PRINT_MSG("Running Known Answer Test with wolfProvider"); + err = test_cipher_cts_kat(wcipher, kat_key); + } + } + + /* Interop cipher testing with different lengths */ + if (err == 0) { + PRINT_MSG("CTS Encrypt with OpenSSL (KRB-style)"); + err = test_cipher_cts_krb_enc(ocipher, key, iv, msg1, sizeof(msg1), enc); + } + if (err == 0) { + PRINT_MSG("CTS Decrypt with wolfProvider (KRB-style)"); + err = test_cipher_cts_krb_dec(wcipher, key, iv, msg1, sizeof(msg1), enc, + sizeof(msg1), dec); + } + if (err == 0) { + PRINT_MSG("CTS Encrypt with wolfProvider (KRB-style)"); + err = test_cipher_cts_krb_enc(wcipher, key, iv, msg1, sizeof(msg1), enc); + } + if (err == 0) { + PRINT_MSG("CTS Decrypt with OpenSSL (KRB-style)"); + err = test_cipher_cts_krb_dec(ocipher, key, iv, msg1, sizeof(msg1), enc, + sizeof(msg1), dec); + } + + if (err == 0) { + PRINT_MSG("CTS Encrypt with OpenSSL (KRB-style)"); + err = test_cipher_cts_krb_enc(ocipher, key, iv, msg2, sizeof(msg2), enc); + } + if (err == 0) { + PRINT_MSG("CTS Decrypt with wolfProvider (KRB-style)"); + err = test_cipher_cts_krb_dec(wcipher, key, iv, msg2, sizeof(msg2), enc, + sizeof(msg2), dec); + } + if (err == 0) { + PRINT_MSG("CTS Encrypt with wolfProvider (KRB-style)"); + err = test_cipher_cts_krb_enc(wcipher, key, iv, msg2, sizeof(msg2), enc); + } + if (err == 0) { + PRINT_MSG("CTS Decrypt with OpenSSL (KRB-style)"); + err = test_cipher_cts_krb_dec(ocipher, key, iv, msg2, sizeof(msg2), enc, + sizeof(msg2), dec); + } + + if (err == 0) { + PRINT_MSG("CTS Encrypt with OpenSSL (KRB-style)"); + err = test_cipher_cts_krb_enc(ocipher, key, iv, msg3, sizeof(msg3), enc); + } + if (err == 0) { + PRINT_MSG("CTS Decrypt with wolfProvider (KRB-style)"); + err = test_cipher_cts_krb_dec(wcipher, key, iv, msg3, sizeof(msg3), enc, + sizeof(msg3), dec); + } + if (err == 0) { + PRINT_MSG("CTS Encrypt with wolfProvider (KRB-style)"); + err = test_cipher_cts_krb_enc(wcipher, key, iv, msg3, sizeof(msg3), enc); + } + if (err == 0) { + PRINT_MSG("CTS Decrypt with OpenSSL (KRB-style)"); + err = test_cipher_cts_krb_dec(ocipher, key, iv, msg3, sizeof(msg3), enc, + sizeof(msg3), dec); + } + + /* Error cases */ + if (err == 0) { + PRINT_MSG("CTS Error cases with OpenSSL"); + err = test_cipher_cts_enc_err_cases(ocipher, key, iv, msg2, sizeof(msg2), enc); + } + if (err == 0) { + PRINT_MSG("CTS Error cases with wolfProvider"); + err = test_cipher_cts_enc_err_cases(wcipher, key, iv, msg2, sizeof(msg2), enc); + } + + EVP_CIPHER_free(wcipher); + EVP_CIPHER_free(ocipher); + + return err; +} + +int test_aes128_cts(void *data) +{ + return test_cipher_cts(data, "AES-128-CBC-CTS", 16); +} + +int test_aes256_cts(void *data) +{ + return test_cipher_cts(data, "AES-256-CBC-CTS", 32); +} + +#endif /* WP_HAVE_AESCTS */ + diff --git a/test/unit.c b/test/unit.c index 2249cfd8..39a957c5 100644 --- a/test/unit.c +++ b/test/unit.c @@ -128,6 +128,10 @@ TEST_CASE test_case[] = { TEST_DECL(test_aes128_cfb_stream, NULL), TEST_DECL(test_aes192_cfb_stream, NULL), TEST_DECL(test_aes256_cfb_stream, NULL), +#endif +#ifdef WP_HAVE_AESCTS + TEST_DECL(test_aes128_cts, NULL), + TEST_DECL(test_aes256_cts, NULL), #endif TEST_DECL(test_cipher_null_zero, NULL), #ifdef WP_HAVE_AESGCM diff --git a/test/unit.h b/test/unit.h index c836ed46..94e05307 100644 --- a/test/unit.h +++ b/test/unit.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -195,6 +196,13 @@ int test_aes128_ccm_tls(void *data); #endif /* WP_HAVE_AESCCM */ +#ifdef WP_HAVE_AESCTS + +int test_aes128_cts(void *data); +int test_aes256_cts(void *data); + +#endif + #ifdef WP_HAVE_RANDOM int test_random(void *data);